// GattClientDlg.cpp : implementation file
//

#include "stdafx.h"
#include "GattClient.h"
#include "GattClientDlg.h"

#ifdef _DEBUG
#define new DEBUG_NEW
#endif


CString IntToHex(const int i)
{
	CString s;
	s.Format(_T("%.8X"), i);
	return s;
}

CString IntToHex(const unsigned char i)
{
	CString s;
	s.Format(_T("%.2X"), i);
	return s;
}

CString IntToHex(const unsigned short i)
{
	CString s;
	s.Format(_T("%.4X"), i);
	return s;
}

CString IntToHex(const __int64 i)
{
	CString s;
	s.Format(_T("%.4X%.8X"), static_cast<INT32>((i >> 32) & 0x00000FFFF),
		static_cast<INT32>(i) & 0xFFFFFFFF);
	return s;
}

__int64 StrToInt64(const CString& s)
{
	return _tcstoi64(s, NULL, 16);
}

int StrToInt(const CString& s)
{
	return _tcstol(s, NULL, 10);
}

CString IntToStr(const int i)
{
	CString s;
	s.Format(_T("%d"), i);
	return s;
}

CString GUIDToString(const GUID& i)
{
	LPOLESTR Guid;
	StringFromCLSID(i, &Guid);
	CString r(Guid);
	CoTaskMemFree(Guid);
	return r;
}

CString BoolToStr(const bool i)
{
	if (i)
		return _T("True");
	return _T("False");
}


// CGattClientDlg dialog




CGattClientDlg::CGattClientDlg(CWnd* pParent /*=NULL*/)
	: CDialog(CGattClientDlg::IDD, pParent)
{
	m_hIcon = AfxGetApp()->LoadIcon(IDR_MAINFRAME);
}

void CGattClientDlg::DoDataExchange(CDataExchange* pDX)
{
	CDialog::DoDataExchange(pDX);
	DDX_Control(pDX, IDC_COMBO_OPERATION_FLAG, cbOperationFlag);
	DDX_Control(pDX, IDC_LIST_DEVICES, lvDevices);
	DDX_Control(pDX, IDC_LIST_SERVICES, lvServices);
	DDX_Control(pDX, IDC_EDIT_CHAR_VALUE, edCharValue);
	DDX_Control(pDX, IDC_LIST_CHARACTERISTICS, lvCharacteristics);
	DDX_Control(pDX, IDC_LIST_DESCRIPTORS, lvDescriptors);
	DDX_Control(pDX, IDC_LIST_DESCRIPTOR_VALUE, lvDescriptorValue);
	DDX_Control(pDX, IDC_LIST_EVENTS, lvEvents);
	DDX_Control(pDX, IDC_COMBO_PROTECTION, cbProtection);
	DDX_Control(pDX, IDC_CHECK_CONNECT_ON_READ, cbConnectOnRead);
	DDX_Control(pDX, IDC_CHECK_FAST_SUBSCRIBE, cbFastSubscribe);
	DDX_Control(pDX, IDC_BUTTON_WRITE_CCCD_SUBSCRIBE, btCCCDSubscribe);
	DDX_Control(pDX, IDC_BUTTON_WRITE_CCCD_UNSUBSCRIBE, btCCCDUnsubscribe);
	DDX_Control(pDX, IDC_CHECK_FORCE_INDICATIONS, cbForceIndications);
	DDX_Control(pDX, IDC_COMBO_PARAMS, cbParams);
	DDX_Control(pDX, IDC_COMBO_WRITE_KIND, cbWriteKind);
	DDX_Control(pDX, IDC_COMBO_SUBSCRIBE_KIND, cbSubscribeKind);
	DDX_Control(pDX, IDC_COMBO_MITM, cbMitm);
	DDX_Control(pDX, IDC_COMBO_IO_CAP, cbIoCap);
}

BEGIN_MESSAGE_MAP(CGattClientDlg, CDialog)
	ON_WM_PAINT()
	ON_WM_QUERYDRAGICON()
	//}}AFX_MSG_MAP
	ON_WM_DESTROY()
	ON_BN_CLICKED(IDC_BUTTON_DISCOVER, &CGattClientDlg::OnBnClickedButtonDiscover)
	ON_BN_CLICKED(IDC_BUTTON_CLEAR, &CGattClientDlg::OnBnClickedButtonClear)
	ON_BN_CLICKED(IDC_BUTTON_CONNECT, &CGattClientDlg::OnBnClickedButtonConnect)
	ON_BN_CLICKED(IDC_BUTTON_DISCONNECT, &CGattClientDlg::OnBnClickedButtonDisconnect)
	ON_BN_CLICKED(IDC_BUTTON_GET_SERVICES, &CGattClientDlg::OnBnClickedButtonGetServices)
	ON_BN_CLICKED(IDC_BUTTON_GET_INCLUDED_SERVICES, &CGattClientDlg::OnBnClickedButtonGetIncludedServices)
	ON_BN_CLICKED(IDC_BUTTON_GET_CHARACTERISTICS, &CGattClientDlg::OnBnClickedButtonGetCharacteristics)
	ON_BN_CLICKED(IDC_BUTTON_GET_CHAR_VALUE, &CGattClientDlg::OnBnClickedButtonGetCharValue)
	ON_BN_CLICKED(IDC_BUTTON_GET_DESCRIPTORS, &CGattClientDlg::OnBnClickedButtonGetDescriptors)
	ON_BN_CLICKED(IDC_BUTTON_GET_DESC_VALUE, &CGattClientDlg::OnBnClickedButtonGetDescValue)
	ON_BN_CLICKED(IDC_BUTTON_SET_CHAR_VALUE, &CGattClientDlg::OnBnClickedButtonSetCharValue)
	ON_BN_CLICKED(IDC_BUTTON_SUBSCRIBE, &CGattClientDlg::OnBnClickedButtonSubscribe)
	ON_BN_CLICKED(IDC_BUTTON_UNSUBSCRIBE, &CGattClientDlg::OnBnClickedButtonUnsubscribe)
	ON_BN_CLICKED(IDC_BUTTON_WRITE_CCCD_SUBSCRIBE, &CGattClientDlg::OnBnClickedButtonWriteCccdSubscribe)
	ON_BN_CLICKED(IDC_BUTTON_WRITE_CCCD_UNSUBSCRIBE, &CGattClientDlg::OnBnClickedButtonWriteCccdUnsubscribe)
	ON_BN_CLICKED(IDC_BUTTON_TERMINATE, &CGattClientDlg::OnBnClickedButtonTerminate)
	ON_BN_CLICKED(IDC_BUTTON_PAIR, &CGattClientDlg::OnBnClickedButtonPair)
	ON_BN_CLICKED(IDC_CHECK_FAST_SUBSCRIBE, &CGattClientDlg::OnBnClickedCheckFastSubscribe)
	ON_BN_CLICKED(IDC_BUTTON_GET_PARAMS, &CGattClientDlg::OnBnClickedButtonGetParams)
	ON_BN_CLICKED(IDC_BUTTON_SET_PARAMS, &CGattClientDlg::OnBnClickedButtonSetParams)
	ON_BN_CLICKED(IDC_BUTTON_GET_MAX_PDU_SIZE, &CGattClientDlg::OnBnClickedButtonGetMaxPduSize)
	ON_BN_CLICKED(IDC_BUTTON_PHY, &CGattClientDlg::OnBnClickedButtonPhy)
	ON_BN_CLICKED(IDC_BUTTON_UNPAIR, &CGattClientDlg::OnBnClickedButtonUnpair)
	ON_BN_CLICKED(IDC_BUTTON_ENUM_PAIRED, &CGattClientDlg::OnBnClickedButtonEnumPaired)
END_MESSAGE_MAP()


// CGattClientDlg message handlers

BOOL CGattClientDlg::OnInitDialog()
{
	CDialog::OnInitDialog();

	// Set the icon for this dialog.  The framework does this automatically
	//  when the application's main window is not a dialog
	SetIcon(m_hIcon, TRUE);			// Set big icon
	SetIcon(m_hIcon, FALSE);		// Set small icon

	// TODO: Add extra initialization here

	__hook(&CwclBluetoothManager::OnNumericComparison, &wclBluetoothManager,
		&CGattClientDlg::wclBluetoothManagerNumericComparison);
	__hook(&CwclBluetoothManager::OnPasskeyNotification, &wclBluetoothManager,
		&CGattClientDlg::wclBluetoothManagerPasskeyNotification);
	__hook(&CwclBluetoothManager::OnPasskeyRequest, &wclBluetoothManager,
		&CGattClientDlg::wclBluetoothManagerPasskeyRequest);
	__hook(&CwclBluetoothManager::OnPinRequest, &wclBluetoothManager,
		&CGattClientDlg::wclBluetoothManagerPinRequest);
	__hook(&CwclBluetoothManager::OnAuthenticationCompleted, &wclBluetoothManager,
		&CGattClientDlg::wclBluetoothManagerAuthenticationCompleted);
	__hook(&CwclBluetoothManager::OnConfirm, &wclBluetoothManager,
		&CGattClientDlg::wclBluetoothManagerConfirm);
	__hook(&CwclBluetoothManager::OnProtectionLevelRequest, &wclBluetoothManager,
		&CGattClientDlg::wclBluetoothManagerProtectionLevelRequest);
	__hook(&CwclBluetoothManager::OnDeviceFound, &wclBluetoothManager,
		&CGattClientDlg::wclBluetoothManagerDeviceFound);
	__hook(&CwclBluetoothManager::OnDiscoveringCompleted, &wclBluetoothManager,
		&CGattClientDlg::wclBluetoothManagerDiscoveringCompleted);
	__hook(&CwclBluetoothManager::OnDiscoveringStarted, &wclBluetoothManager,
		&CGattClientDlg::wclBluetoothManagerDiscoveringStarted);
	__hook(&CwclBluetoothManager::OnIoCapabilityRequest, &wclBluetoothManager,
		&CGattClientDlg::wclBluetoothManagerIoCapabilityRequest);

	__hook(&CwclGattClient::OnCharacteristicChanged, &wclGattClient,
		&CGattClientDlg::wclGattClientCharacteristicChanged);
	__hook(&CwclGattClient::OnConnect, &wclGattClient, &CGattClientDlg::wclGattClientConnect);
	__hook(&CwclGattClient::OnDisconnect, &wclGattClient, &CGattClientDlg::wclGattClientDisconnect);
	__hook(&CwclGattClient::OnConnectionParamsChanged, &wclGattClient,
		&CGattClientDlg::wclGattClientConnectionParamsChanged);
	__hook(&CwclGattClient::OnMaxPduSizeChanged, &wclGattClient,
		&CGattClientDlg::wclGattClientMaxPduSizeChanged);
	__hook(&CwclGattClient::OnConnectionPhyChanged, &wclGattClient,
		&CGattClientDlg::wclGattClientConnectionPhyChanged);

	cbOperationFlag.InsertString(0, _T("None"));
	cbOperationFlag.InsertString(1, _T("Force Read From Device"));
	cbOperationFlag.InsertString(2, _T("Force Read From Cache"));
	cbOperationFlag.SetCurSel(0);

	cbProtection.InsertString(0, _T("None"));
	cbProtection.InsertString(1, _T("Authentication"));
	cbProtection.InsertString(2, _T("Encryption"));
	cbProtection.InsertString(3, _T("Encryption And Authentication"));
	cbProtection.SetCurSel(0);

	cbParams.InsertString(0, _T("Balanced"));
	cbParams.InsertString(1, _T("PowerOptimized"));
	cbParams.InsertString(2, _T("ThroughputOptimized"));
	cbParams.InsertString(3, _T("Custom"));
	cbParams.SetCurSel(0);

	cbWriteKind.InsertString(0, _T("With Response"));
	cbWriteKind.InsertString(1, _T("Without Response"));
	cbWriteKind.InsertString(2, _T("Auto"));
	cbWriteKind.SetCurSel(2);

	cbSubscribeKind.InsertString(0, _T("Notification"));
	cbSubscribeKind.InsertString(1, _T("Indication"));
	cbSubscribeKind.InsertString(2, _T("Manual"));
	cbSubscribeKind.SetCurSel(2);

	lvDevices.InsertColumn(0, _T("Address"), 0, 150);
	lvDevices.InsertColumn(1, _T("Name"), 0, 150);
	lvDevices.InsertColumn(2, _T("Device type"), 0, 150);
	lvDevices.InsertColumn(3, _T("Paired"), 0, 80);
	lvDevices.SetExtendedStyle(lvDevices.GetExtendedStyle() | LVS_EX_FULLROWSELECT);

	lvServices.InsertColumn(0, _T("UUID"), 0, 200);
	lvServices.InsertColumn(1, _T("Is short UUID"), 0, 80);
	lvServices.InsertColumn(2, _T("Handle"), 0, 100);
	lvServices.SetExtendedStyle(lvServices.GetExtendedStyle() | LVS_EX_FULLROWSELECT);

	lvCharacteristics.InsertColumn(0, _T("UUID"), 0, 200);
	lvCharacteristics.InsertColumn(1, _T("Is short UUID"), 0, 80);
	lvCharacteristics.InsertColumn(2, _T("Service handle"), 0, 90);
	lvCharacteristics.InsertColumn(3, _T("Handle"), 0, 100);
	lvCharacteristics.InsertColumn(4, _T("Value handle"), 0, 100);
	lvCharacteristics.InsertColumn(5, _T("Is broadcastable"), 0, 80);
	lvCharacteristics.InsertColumn(6, _T("Is readable"), 0, 80);
	lvCharacteristics.InsertColumn(7, _T("Is writable"), 0, 80);
	lvCharacteristics.InsertColumn(8, _T("Is writable without response"), 0, 80);
	lvCharacteristics.InsertColumn(9, _T("Is signed writable"), 0, 80);
	lvCharacteristics.InsertColumn(10, _T("Is notifiable"), 0, 80);
	lvCharacteristics.InsertColumn(11, _T("Is indicatable"), 0, 80);
	lvCharacteristics.InsertColumn(12, _T("Has extended properties"), 0, 80);
	lvCharacteristics.SetExtendedStyle(lvCharacteristics.GetExtendedStyle() | LVS_EX_FULLROWSELECT);

	lvDescriptors.InsertColumn(0, _T("UUID"), 0, 200);
	lvDescriptors.InsertColumn(1, _T("Is short UUID"), 0, 80);
	lvDescriptors.InsertColumn(2, _T("Service handle"), 0, 80);
	lvDescriptors.InsertColumn(3, _T("Characteristic handle"), 0, 80);
	lvDescriptors.InsertColumn(4, _T("Handle"), 0, 100);
	lvDescriptors.InsertColumn(5, _T("Type"), 0, 100);
	lvDescriptors.SetExtendedStyle(lvDescriptors.GetExtendedStyle() | LVS_EX_FULLROWSELECT);

	lvDescriptorValue.InsertColumn(0, _T("Property name"), 0, 200);
	lvDescriptorValue.InsertColumn(1, _T("Property value"), 0, 200);
	lvDescriptorValue.SetExtendedStyle(lvDescriptorValue.GetExtendedStyle() | LVS_EX_FULLROWSELECT);

	lvEvents.InsertColumn(0, _T("Address"), 0, 100);
	lvEvents.InsertColumn(1, _T("Event"), 0, 150);
	lvEvents.InsertColumn(2, _T("Param"), 0, 150);
	lvEvents.InsertColumn(3, _T("Value"), 0, 100);
	lvEvents.SetExtendedStyle(lvEvents.GetExtendedStyle() | LVS_EX_FULLROWSELECT);

	cbMitm.AddString(_T("mitmProtectionNotRequired"));
	cbMitm.AddString(_T("mitmProtectionRequired"));
	cbMitm.AddString(_T("mitmProtectionNotRequiredBonding"));
	cbMitm.AddString(_T("mitmProtectionRequiredBonding"));
	cbMitm.AddString(_T("mitmProtectionNotRequiredGeneralBonding"));
	cbMitm.AddString(_T("mitmProtectionRequiredGeneralBonding"));
	cbMitm.AddString(_T("mitmProtectionNotDefined"));
	cbMitm.SetCurSel(0);

	cbIoCap.AddString(_T("iocapDisplayOnly"));
	cbIoCap.AddString(_T("iocapDisplayYesNo"));
	cbIoCap.AddString(_T("iocapKeyboardOnly"));
	cbIoCap.AddString(_T("iocapNoInputNoOutput"));
	cbIoCap.AddString(_T("iocapDisplayKeyboard"));
	cbIoCap.AddString(_T("iocapNotDefined"));
	cbIoCap.SetCurSel(0);

	edCharValue.SetWindowText(_T("00FFAA00"));

	wclBluetoothManager.Open();
	Cleanup();

	return TRUE;  // return TRUE  unless you set the focus to a control
}

// If you add a minimize button to your dialog, you will need the code below
//  to draw the icon.  For MFC applications using the document/view model,
//  this is automatically done for you by the framework.

void CGattClientDlg::OnPaint()
{
	if (IsIconic())
	{
		CPaintDC dc(this); // device context for painting

		SendMessage(WM_ICONERASEBKGND, reinterpret_cast<WPARAM>(dc.GetSafeHdc()), 0);

		// Center icon in client rectangle
		int cxIcon = GetSystemMetrics(SM_CXICON);
		int cyIcon = GetSystemMetrics(SM_CYICON);
		CRect rect;
		GetClientRect(&rect);
		int x = (rect.Width() - cxIcon + 1) / 2;
		int y = (rect.Height() - cyIcon + 1) / 2;

		// Draw the icon
		dc.DrawIcon(x, y, m_hIcon);
	}
	else
	{
		CDialog::OnPaint();
	}
}

// The system calls this function to obtain the cursor to display while the user drags
//  the minimized window.
HCURSOR CGattClientDlg::OnQueryDragIcon()
{
	return static_cast<HCURSOR>(m_hIcon);
}


void CGattClientDlg::OnDestroy()
{
	CDialog::OnDestroy();

	wclGattClient.Disconnect();
	wclBluetoothManager.Close();

	// On Visual Studio 2015 and above the __unhook() poroduces warning C4458
	// We we have to supress it. By some reason it can not be disabled by pragma
	// so we disabled it in the project settings.
	__unhook(&wclBluetoothManager);
	__unhook(&wclGattClient);
	
	Cleanup();
}

CwclBluetoothRadio* CGattClientDlg::GetRadio()
{
	CwclBluetoothRadio* Radio;
	int Res = wclBluetoothManager.GetLeRadio(Radio);
	if (Res != WCL_E_SUCCESS)
	{
		AfxMessageBox(_T("Get working radio failed"));
		return NULL;
	}
	return Radio;
}

void CGattClientDlg::OnBnClickedButtonDiscover()
{
	CwclBluetoothRadio* Radio = GetRadio();
	if (Radio != NULL)
	{
		int Res = Radio->Discover(10, dkBle);
		if (Res != WCL_E_SUCCESS)
			AfxMessageBox(_T("Error starting discovering: 0x") + IntToHex(Res));
	}
}

void CGattClientDlg::OnBnClickedButtonClear()
{
	lvEvents.DeleteAllItems();
}

void CGattClientDlg::TraceEvent(const __int64 Address, const CString& Event,
	const CString& Param, const CString& Value)
{
	int Item = lvEvents.GetItemCount();
	if (Address == 0)
		lvEvents.InsertItem(Item, _T(""));
	else
		lvEvents.InsertItem(Item, IntToHex(Address));
	lvEvents.SetItemText(Item, 1, Event);
	lvEvents.SetItemText(Item, 2, Param);
	lvEvents.SetItemText(Item, 3, Value);
}

void CGattClientDlg::OnBnClickedButtonConnect()
{
	POSITION Pos = lvDevices.GetFirstSelectedItemPosition();
	if (Pos == NULL)
		AfxMessageBox(_T("Select device"));
	else
	{
		int Item = lvDevices.GetNextSelectedItem(Pos);
		try
		{
			wclGattClient.Address = StrToInt64(lvDevices.GetItemText(Item, 0));
			wclGattClient.ConnectOnRead = (cbConnectOnRead.GetCheck() != FALSE);
			wclGattClient.ForceNotifications = (cbForceIndications.GetCheck() != FALSE);
			int Res = wclGattClient.Connect((CwclBluetoothRadio*)lvDevices.GetItemData(Item));
			if (Res != WCL_E_SUCCESS)
				AfxMessageBox(_T("Error: 0x") + IntToHex(Res));
		}
		catch(wclException* ex)
		{
			MessageBoxA(NULL, ex->what(), "ERROR", 0);
		}
	}
}

void CGattClientDlg::OnBnClickedButtonDisconnect()
{
	int Res = wclGattClient.Disconnect();
	if (Res != WCL_E_SUCCESS)
		AfxMessageBox(_T("Error: 0x") + IntToHex(Res));
}

void CGattClientDlg::OnBnClickedButtonGetServices()
{
	lvServices.DeleteAllItems();
	FServices.clear();
	
	int Res = wclGattClient.ReadServices(OpFlag(), FServices);
	if (Res != WCL_E_SUCCESS)
	{
		AfxMessageBox(_T("Error: 0x") + IntToHex(Res));
		return;
	}
	
	if (FServices.size() == 0)
		return;
	
	for (wclGattServices::iterator i = FServices.begin(); i != FServices.end(); i++)
	{
		wclGattService Service = (*i);
		
		int Item = lvServices.GetItemCount();
		if (Service.Uuid.IsShortUuid)
			lvServices.InsertItem(Item, IntToHex(Service.Uuid.ShortUuid));
		else
			lvServices.InsertItem(Item, GUIDToString(Service.Uuid.LongUuid));
		lvServices.SetItemText(Item, 1, BoolToStr(Service.Uuid.IsShortUuid));
		lvServices.SetItemText(Item, 2, IntToHex(Service.Handle));
	}
}

void CGattClientDlg::OnBnClickedButtonGetIncludedServices()
{
	POSITION Pos = lvServices.GetFirstSelectedItemPosition();
	if (Pos == NULL)
	{
		AfxMessageBox(_T("Select service"));
		return;
	}
	
	wclGattServices::iterator it = FServices.begin();
	std::advance(it, lvServices.GetNextSelectedItem(Pos));
	wclGattService Service = (*it);
	
	lvServices.DeleteAllItems();
	FServices.clear();
	
	int Res = wclGattClient.ReadIncludedServices(Service, OpFlag(), FServices);
	if (Res != WCL_E_SUCCESS)
	{
		AfxMessageBox(_T("Error: 0x") + IntToHex(Res));
		return;
	}
	
	if (FServices.size() == 0)
		return;
	
	for (wclGattServices::iterator i = FServices.begin(); i != FServices.end(); i++)
	{
		Service = (*i);
		
		int Item = lvServices.GetItemCount();
		if (Service.Uuid.IsShortUuid)
			lvServices.InsertItem(Item, IntToHex(Service.Uuid.ShortUuid));
		else
			lvServices.InsertItem(Item, GUIDToString(Service.Uuid.LongUuid));
		lvServices.SetItemText(Item, 1, BoolToStr(Service.Uuid.IsShortUuid));
		lvServices.SetItemText(Item, 2, IntToHex(Service.Handle));
	}
}

void CGattClientDlg::OnBnClickedButtonGetCharacteristics()
{
	FCharacteristics.clear();
	lvCharacteristics.DeleteAllItems();
	
	POSITION Pos = lvServices.GetFirstSelectedItemPosition();
	if (Pos == NULL)
	{
		AfxMessageBox(_T("Select service"));
		return;
	}

	wclGattServices::iterator it = FServices.begin();
	std::advance(it, lvServices.GetNextSelectedItem(Pos));
	wclGattService Service = (*it);
	
	int Res = wclGattClient.ReadCharacteristics(Service, OpFlag(), FCharacteristics);
	if (Res != WCL_E_SUCCESS)
	{
		AfxMessageBox(_T("Error: 0x") + IntToHex(Res));
		return;
	}
	
	if (FCharacteristics.size() == 0)
		return;
	
	for (wclGattCharacteristics::iterator i = FCharacteristics.begin(); i != FCharacteristics.end(); i++)
	{
		wclGattCharacteristic Character = (*i);
		
		int Item = lvCharacteristics.GetItemCount();
		if (Character.Uuid.IsShortUuid)
			lvCharacteristics.InsertItem(Item, IntToHex(Character.Uuid.ShortUuid));
		else
			lvCharacteristics.InsertItem(Item, GUIDToString(Character.Uuid.LongUuid));
		lvCharacteristics.SetItemText(Item, 1, BoolToStr(Character.Uuid.IsShortUuid));
		lvCharacteristics.SetItemText(Item, 2, IntToHex(Character.ServiceHandle));
		lvCharacteristics.SetItemText(Item, 3, IntToHex(Character.Handle));
		lvCharacteristics.SetItemText(Item, 4, IntToHex(Character.ValueHandle));
		lvCharacteristics.SetItemText(Item, 5, BoolToStr(Character.IsBroadcastable));
		lvCharacteristics.SetItemText(Item, 6, BoolToStr(Character.IsReadable));
		lvCharacteristics.SetItemText(Item, 7, BoolToStr(Character.IsWritable));
		lvCharacteristics.SetItemText(Item, 8, BoolToStr(Character.IsWritableWithoutResponse));
		lvCharacteristics.SetItemText(Item, 9, BoolToStr(Character.IsSignedWritable));
		lvCharacteristics.SetItemText(Item, 10, BoolToStr(Character.IsNotifiable));
		lvCharacteristics.SetItemText(Item, 11, BoolToStr(Character.IsIndicatable));
		lvCharacteristics.SetItemText(Item, 12, BoolToStr(Character.HasExtendedProperties));
	}
}

void CGattClientDlg::OnBnClickedButtonGetCharValue()
{
	POSITION Pos = lvCharacteristics.GetFirstSelectedItemPosition();
	if (Pos == NULL)
	{
		AfxMessageBox(_T("Select characteristic"));
		return;
	}

	wclGattCharacteristics::iterator it = FCharacteristics.begin();
	std::advance(it, lvCharacteristics.GetNextSelectedItem(Pos));
	wclGattCharacteristic Characteristic = (*it);
	
	unsigned char* Value;
	unsigned long Length;
	int Res = wclGattClient.ReadCharacteristicValue(Characteristic, OpFlag(), Value,
		Length, Protection());
	if (Res != WCL_E_SUCCESS)
	{
		AfxMessageBox(_T("Error: 0x") + IntToHex(Res));
		return;
	}
	
	if (Value == NULL || Length == 0)
	{
		AfxMessageBox(_T("Value is empty"));
		return;
	}
	
	CString Str = _T("");
	for (unsigned long i = 0; i < Length; i++)
		Str = Str + IntToHex(Value[i]);
	AfxMessageBox(_T("Value: ") + Str);

	if (Value != NULL)
		free(Value);
}

void CGattClientDlg::OnBnClickedButtonGetDescriptors()
{
	FDescriptors.clear();
	lvDescriptors.DeleteAllItems();
	
	POSITION Pos = lvCharacteristics.GetFirstSelectedItemPosition();
	if (Pos == NULL)
	{
		AfxMessageBox(_T("Select characteristic"));
		return;
	}

	wclGattCharacteristics::iterator it = FCharacteristics.begin();
	std::advance(it, lvCharacteristics.GetNextSelectedItem(Pos));
	wclGattCharacteristic Characteristic = (*it);
	
	int Res = wclGattClient.ReadDescriptors(Characteristic, OpFlag(), FDescriptors);
	if (Res != WCL_E_SUCCESS)
	{
		AfxMessageBox(_T("Error: 0x") + IntToHex(Res));
		return;
	}
	
	if (FDescriptors.size() == 0)
		return;
	
	for (wclGattDescriptors::iterator i = FDescriptors.begin(); i != FDescriptors.end(); i++)
	{
		wclGattDescriptor Descriptor = (*i);
		
		int Item = lvDescriptors.GetItemCount();
		if (Descriptor.Uuid.IsShortUuid)
			lvDescriptors.InsertItem(Item, IntToHex(Descriptor.Uuid.ShortUuid));
		else
			lvDescriptors.InsertItem(Item, GUIDToString(Descriptor.Uuid.LongUuid));
		lvDescriptors.SetItemText(Item, 1, BoolToStr(Descriptor.Uuid.IsShortUuid));
		lvDescriptors.SetItemText(Item, 2, IntToHex(Descriptor.ServiceHandle));
		lvDescriptors.SetItemText(Item, 3, IntToHex(Descriptor.CharacteristicHandle));
		lvDescriptors.SetItemText(Item, 4, IntToHex(Descriptor.Handle));
		switch (Descriptor.DescriptorType)
		{
			case dtCharacteristicExtendedProperties:
				lvDescriptors.SetItemText(Item, 5, _T("dtCharacteristicExtendedProperties"));
				break;
			case dtCharacteristicUserDescription:
				lvDescriptors.SetItemText(Item, 5, _T("dtCharacteristicUserDescription"));
				break;
			case dtClientCharacteristicConfiguration:
				lvDescriptors.SetItemText(Item, 5, _T("dtClientCharacteristicConfiguration"));
				break;
			case dtServerCharacteristicConfiguration:
				lvDescriptors.SetItemText(Item, 5, _T("dtServerCharacteristicConfiguration"));
				break;
			case dtCharacteristicFormat:
				lvDescriptors.SetItemText(Item, 5, _T("dtCharacteristicFormat"));
				break;
			case dtCharacteristicAggregateFormat:
				lvDescriptors.SetItemText(Item, 5, _T("dtCharacteristicAggregateFormat"));
				break;
			default:
				lvDescriptors.SetItemText(Item, 5, _T("dtCustomDescriptor"));
				break;
		}
	}
}

void CGattClientDlg::Cleanup()
{
	FCharacteristics.clear();
	FDescriptors.clear();
	FServices.clear();
	
	lvServices.DeleteAllItems();
	lvCharacteristics.DeleteAllItems();
	lvDescriptors.DeleteAllItems();
	lvDescriptorValue.DeleteAllItems();
}

void CGattClientDlg::OnBnClickedButtonGetDescValue()
{
	lvDescriptorValue.DeleteAllItems();

	POSITION Pos = lvDescriptors.GetFirstSelectedItemPosition();
	if (Pos == NULL)
	{
		AfxMessageBox(_T("Select descriptor"));
		return;
	}

	wclGattDescriptors::iterator it = FDescriptors.begin();
	std::advance(it, lvDescriptors.GetNextSelectedItem(Pos));
	wclGattDescriptor Descriptor = (*it);
	
	wclGattDescriptorValue Value;
	int Res = wclGattClient.ReadDescriptorValue(Descriptor, OpFlag(), Value, Protection());
	if (Res != WCL_E_SUCCESS)
		AfxMessageBox(_T("Error: 0x") + IntToHex(Res));
	else
	{
		switch (Value.AType)
		{
			case dtCharacteristicExtendedProperties:
				ListProp(_T("Type"), _T("dtCharacteristicExtendedProperties"));
				ListProp(_T("IsReliableWriteEnabled"), BoolToStr(Value.CharacteristicExtendedProperties.IsReliableWriteEnabled));
				ListProp(_T("IsAuxiliariesWritable"), BoolToStr(Value.CharacteristicExtendedProperties.IsAuxiliariesWritable));
				break;

			case dtCharacteristicUserDescription:
				ListProp(_T("Type"), _T("dtCharacteristicUserDescription"));
				ListProp(_T("Descriptipon"), Value.UserDescription.Description.c_str());
				break;

			case dtClientCharacteristicConfiguration:
				ListProp(_T("Type"), _T("dtClientCharacteristicConfiguration"));
				ListProp(_T("IsSubscribeToNotification"), BoolToStr(Value.ClientCharacteristicConfiguration.IsSubscribeToNotification));
				ListProp(_T("IsSubscribeToIndication"), BoolToStr(Value.ClientCharacteristicConfiguration.IsSubscribeToIndication));
				break;

			case dtServerCharacteristicConfiguration:
				ListProp(_T("Type"), _T("dtServerCharacteristicConfiguration"));
				ListProp(_T("IsBroadcast"), BoolToStr(Value.ServerCharacteristicConfiguration.IsBroadcast));
				break;
					
			case dtCharacteristicFormat:
				ListProp(_T("Type"), _T("dtCharacteristicFormat"));
				ListProp(_T("Format"), IntToHex((unsigned char)Value.CharacteristicFormat.Format));
				ListProp(_T("Exponent"), IntToHex(Value.CharacteristicFormat.Exponent));
				ListProp(_T("Unit"), IntToHex(Value.CharacteristicFormat.AUnit));
				ListProp(_T("NameSpace"), IntToHex(Value.CharacteristicFormat.NameSpace));
				ListProp(_T("Description"), IntToHex(Value.CharacteristicFormat.Description));
				break;
			
			case dtCharacteristicAggregateFormat:
				ListProp(_T("Type"), _T("dtCharacteristicAggregateFormat"));
				if (Value.CharacteristicAggregateFormat.Handles.size() > 0)
				{
					unsigned char Ndx = 0;
					for (std::vector<unsigned short>::iterator i = Value.CharacteristicAggregateFormat.Handles.begin();
						i != Value.CharacteristicAggregateFormat.Handles.end(); i++)
					{
						ListProp(_T("Handle [") + IntToStr(Ndx) + _T("]"), IntToHex(*i));
						Ndx++;
					}
				}
				break;

			default:
				ListProp(_T("Type"), _T("UNKNOWN"));
		}
			
		if (Value.Length > 0)
		{
			CString Str = _T("");
			for (unsigned long i = 0; i < Value.Length; i++)
				Str = Str + IntToHex(Value.Data[i]);
			ListProp(_T("Data"), Str);
		}
		
		if (Value.Data != NULL)
			free(Value.Data);
	}
}

void CGattClientDlg::ListProp(const CString& Name, const CString& Value)
{
	int Item = lvDescriptorValue.GetItemCount();
	lvDescriptorValue.InsertItem(Item, Name);
	lvDescriptorValue.SetItemText(Item, 1, Value);
}

void CGattClientDlg::OnBnClickedButtonSetCharValue()
{
	POSITION Pos = lvCharacteristics.GetFirstSelectedItemPosition();
	if (Pos == NULL)
	{
		AfxMessageBox(_T("Select characteristic"));
		return;
	}

	wclGattCharacteristics::iterator it = FCharacteristics.begin();
	std::advance(it, lvCharacteristics.GetNextSelectedItem(Pos));
	wclGattCharacteristic Characteristic = (*it);
	
	CString Str;
	edCharValue.GetWindowText(Str);
	if (Str.GetLength() % 2 != 0)
		Str = _T("0") + Str;

	unsigned long Length = Str.GetLength() / 2;
	unsigned char* Val = (unsigned char*)malloc(Length);
	if (Val == 0)
		AfxMessageBox(_T("Memory allocation failed"));
	else
	{
		int i = 0;
		int j = 0;
		while (i < Str.GetLength())
		{
			unsigned char b = LOBYTE(LOWORD(_tcstol(Str.Mid(i, 2), NULL, 16)));
			Val[j] = b;
			i += 2;
			j++;
		}

		int Res;
		wclGattWriteKind WriteKind;
		switch (cbWriteKind.GetCurSel())
		{
			case 0:
				WriteKind = wkWithResponse;
				break;
			case 1:
				WriteKind = wkWithoutResponse;
				break;
			default:
				WriteKind = wkAuto;
				break;
		}
		
		Res = wclGattClient.WriteCharacteristicValue(Characteristic, Val,
			Length, Protection(), WriteKind);
		if (Res != WCL_E_SUCCESS)
			AfxMessageBox(_T("Error: 0x") + IntToHex(Res));

		free(Val);
	}
}

void CGattClientDlg::OnBnClickedButtonSubscribe()
{
	POSITION Pos = lvCharacteristics.GetFirstSelectedItemPosition();
	if (Pos == NULL)
	{
		AfxMessageBox(_T("Select characteristic"));
		return;
	}

	wclGattCharacteristics::iterator it = FCharacteristics.begin();
	std::advance(it, lvCharacteristics.GetNextSelectedItem(Pos));
	wclGattCharacteristic Characteristic = (*it);

	wclGattSubscribeKind SubscribeKind;
	switch (cbSubscribeKind.GetCurSel())
	{
		case 0:
			SubscribeKind = skNotification;
			break;
		case 1:
			SubscribeKind = skIndication;
			break;
		default:
			SubscribeKind = skManual;
			break;
	}

	if (SubscribeKind == skManual)
	{
		// In case if characteristic has both Indication and Notification properties
		// set to True we have to select one of them. Here we use Notifications but
		// you can use other one.
		if (Characteristic.IsNotifiable && Characteristic.IsIndicatable)
		{
			// Change the code line below to
			// Characteristic.IsNotifiable = false;
			// if you want to receive Indications instead of notifications.
			Characteristic.IsIndicatable = false;
		}
	}

	int Res;
	if (cbFastSubscribe.GetCheck())
	{
		if (SubscribeKind == skManual)
			Res = wclGattClient.SubscribeForNotifications(Characteristic);
		else
		{
			Res = wclGattClient.SubscribeForNotifications(Characteristic, OpFlag(),
				Protection(), SubscribeKind);
		}
	}
	else
	{
		if (SubscribeKind == skManual)
			Res = wclGattClient.Subscribe(Characteristic);
		else
			Res = wclGattClient.Subscribe(Characteristic, SubscribeKind);
	}

	if (Res != WCL_E_SUCCESS)
		AfxMessageBox(_T("Error: 0x") + IntToHex(Res));
}

void CGattClientDlg::OnBnClickedButtonUnsubscribe()
{
	POSITION Pos = lvCharacteristics.GetFirstSelectedItemPosition();
	if (Pos == NULL)
	{
		AfxMessageBox(_T("Select characteristic"));
		return;
	}

	wclGattCharacteristics::iterator it = FCharacteristics.begin();
	std::advance(it, lvCharacteristics.GetNextSelectedItem(Pos));
	wclGattCharacteristic Characteristic = (*it);
	
	wclGattSubscribeKind SubscribeKind;
	switch (cbSubscribeKind.GetCurSel())
	{
		case 0:
			SubscribeKind = skNotification;
			break;
		case 1:
			SubscribeKind = skIndication;
			break;
		default:
			SubscribeKind = skManual;
			break;
	}
	
	if (SubscribeKind == skManual)
	{
		// In case if characteristic has both Indication and Notification properties
		// set to True we have to select one of them. Here we use Notifications but
		// you can use other one.
		// YOU MUST USE THE SAME PROPERTY THAT IS USED IN SUBSCRIBE
		if (Characteristic.IsNotifiable && Characteristic.IsIndicatable)
		{
			// Change the code line below to
			// Characteristic.IsNotifiable = false;
			// if you want to receive Indications instead of notifications.
			Characteristic.IsIndicatable = false;
		}
	}

	int Res;
	if (cbFastSubscribe.GetCheck())
	{
		if (SubscribeKind == skManual)
			Res = wclGattClient.UnsubscribeFromNotifications(Characteristic);
		else
		{
			Res = wclGattClient.UnsubscribeFromNotifications(Characteristic, OpFlag(),
				Protection(), SubscribeKind);
		}
	}
	else
	{
		if (SubscribeKind == skManual)
			Res = wclGattClient.Unsubscribe(Characteristic);
		else
			Res = wclGattClient.Unsubscribe(Characteristic, SubscribeKind);
	}

	if (Res != WCL_E_SUCCESS)
		AfxMessageBox(_T("Error: 0x") + IntToHex(Res));
}

wclGattOperationFlag CGattClientDlg::OpFlag() const
{
	switch (cbOperationFlag.GetCurSel())
	{
		case 1:
			return goReadFromDevice;
		case 2:
			return goReadFromCache;
		default:
			return goNone;
	}
}

void CGattClientDlg::OnBnClickedButtonWriteCccdSubscribe()
{
	POSITION Pos = lvCharacteristics.GetFirstSelectedItemPosition();
	if (Pos == NULL)
	{
		AfxMessageBox(_T("Select characteristic"));
		return;
	}

	wclGattCharacteristics::iterator it = FCharacteristics.begin();
	std::advance(it, lvCharacteristics.GetNextSelectedItem(Pos));
	wclGattCharacteristic Characteristic = (*it);

	wclGattSubscribeKind SubscribeKind;
	switch (cbSubscribeKind.GetCurSel())
	{
		case 0:
			SubscribeKind = skNotification;
			break;
		case 1:
			SubscribeKind = skIndication;
			break;
		default:
			SubscribeKind = skManual;
			break;
	}
	
	int Res;
	if (SubscribeKind == skManual)
	{
		// In case if characteristic has both Indication and Notification properties
		// set to True we have to select one of them. Here we use Notifications but
		// you can use other one.
		if (Characteristic.IsNotifiable && Characteristic.IsIndicatable)
		{
			// Change the code line below to
			// Characteristic.IsNotifiable = false;
			// if you want to receive Indications instead of notifications.
			Characteristic.IsIndicatable = false;
		}
		Res = wclGattClient.WriteClientConfiguration(Characteristic, true, OpFlag(), Protection());
	}
	else
	{
		Res = wclGattClient.WriteClientConfiguration(Characteristic, true, OpFlag(),
			Protection(), SubscribeKind);
	}

	if (Res != WCL_E_SUCCESS)
		AfxMessageBox(_T("Error: 0x") + IntToHex(Res));
}

void CGattClientDlg::OnBnClickedButtonWriteCccdUnsubscribe()
{
	POSITION Pos = lvCharacteristics.GetFirstSelectedItemPosition();
	if (Pos == NULL)
	{
		AfxMessageBox(_T("Select characteristic"));
		return;
	}

	wclGattCharacteristics::iterator it = FCharacteristics.begin();
	std::advance(it, lvCharacteristics.GetNextSelectedItem(Pos));
	wclGattCharacteristic Characteristic = (*it);
	
	wclGattSubscribeKind SubscribeKind;
	switch (cbSubscribeKind.GetCurSel())
	{
		case 0:
			SubscribeKind = skNotification;
			break;
		case 1:
			SubscribeKind = skIndication;
			break;
		default:
			SubscribeKind = skManual;
			break;
	}
	
	int Res;
	if (SubscribeKind == skManual)
	{
		// In case if characteristic has both Indication and Notification properties
		// set to True we have to select one of them. Here we use Notifications but
		// you can use other one.
		// YOU MUST USE THE SAME PROPERTY THAT IS USED IN SUBSCRIBE
		if (Characteristic.IsNotifiable && Characteristic.IsIndicatable)
		{
			// Change the code line below to
			// Characteristic.IsNotifiable = false;
			// if you want to receive Indications instead of notifications.
			Characteristic.IsIndicatable = false;
		}
		Res = wclGattClient.WriteClientConfiguration(Characteristic, false,
			OpFlag(), Protection());
	}
	else
	{
		Res = wclGattClient.WriteClientConfiguration(Characteristic, false, OpFlag(),
			Protection(), SubscribeKind);
	}

	if (Res != WCL_E_SUCCESS)
		AfxMessageBox(_T("Error: 0x") + IntToHex(Res));
}

void CGattClientDlg::wclBluetoothManagerNumericComparison(void* Sender, CwclBluetoothRadio* const Radio,
	const __int64 Address, const unsigned long Number, bool& Confirm)
{
	UNREFERENCED_PARAMETER(Sender);
	UNREFERENCED_PARAMETER(Radio);

	// Accept any pairing.
	Confirm = true;
	TraceEvent(Address, _T("Numeric comparison"), _T("Number"), IntToStr(Number));
}

void CGattClientDlg::wclBluetoothManagerPasskeyNotification(void* Sender, CwclBluetoothRadio* const Radio,
	const __int64 Address, const unsigned long Passkey)
{
	UNREFERENCED_PARAMETER(Sender);
	UNREFERENCED_PARAMETER(Radio);

	TraceEvent(Address, _T("Passkey notification"), _T("Passkey"), IntToStr(Passkey));
}

void CGattClientDlg::wclBluetoothManagerPasskeyRequest(void* Sender, CwclBluetoothRadio* const Radio,
	const __int64 Address, unsigned long& Passkey)
{
	UNREFERENCED_PARAMETER(Sender);
	UNREFERENCED_PARAMETER(Radio);

	// Use 123456 as passkey.
	Passkey = 123456;
	TraceEvent(Address, _T("Passkey request"), _T("Passkey"), IntToStr(Passkey));
}

void CGattClientDlg::wclBluetoothManagerPinRequest(void* Sender, CwclBluetoothRadio* const Radio,
	const __int64 Address, tstring& Pin)
{
	UNREFERENCED_PARAMETER(Sender);
	UNREFERENCED_PARAMETER(Radio);

	// Use '0000' as PIN.
	Pin = _T("0000");
	TraceEvent(Address, _T("Pin request"), _T("PIN"), Pin.c_str());
}

void CGattClientDlg::wclBluetoothManagerDeviceFound(void* Sender, CwclBluetoothRadio* const Radio,
	const __int64 Address)
{
	UNREFERENCED_PARAMETER(Sender);

	int Item = lvDevices.GetItemCount();
	lvDevices.InsertItem(Item, IntToHex(Address));
	lvDevices.SetItemText(Item, 1, _T("")); // We can not read a device's name here.
	lvDevices.SetItemData(Item, (DWORD_PTR)Radio); // To use it later.

	wclBluetoothDeviceType DevType = dtMixed;
	int Res = Radio->GetRemoteDeviceType(Address, DevType);
	if (Res != WCL_E_SUCCESS)
		lvDevices.SetItemText(Item, 2, _T("Error: 0x") + IntToHex(Res));
	else
	{
		switch (DevType)
		{
			case dtClassic:
				lvDevices.SetItemText(Item, 2, _T("Classic"));
				break;
			case dtBle:
				lvDevices.SetItemText(Item, 2, _T("BLE"));
				break;
			case dtMixed:
				lvDevices.SetItemText(Item, 2, _T("Mixed"));
				break;
			default:
				lvDevices.SetItemText(Item, 2, _T("Unknown"));
		}
	}

	bool Paired = false;
	Res = Radio->GetRemotePaired(Address, Paired);
	if (Res != WCL_E_SUCCESS)
		lvDevices.SetItemText(Item, 3, _T("Error: 0x") + IntToHex(Res));
	else
	{
		if (Paired)
			lvDevices.SetItemText(Item, 3, _T("True"));
		else
			lvDevices.SetItemText(Item, 3, _T("False"));
	}
	
	TraceEvent(Address, _T("Device found"), _T(""), _T(""));
}

void CGattClientDlg::wclBluetoothManagerDiscoveringCompleted(void* Sender, CwclBluetoothRadio* const Radio,
	const int Error)
{
	UNREFERENCED_PARAMETER(Sender);
	UNREFERENCED_PARAMETER(Error);

	if (lvDevices.GetItemCount() == 0)
		AfxMessageBox(_T("No BLE devices were found."));
	else
	{
		// Here we can update found devices names.
		for (int i = 0; i < lvDevices.GetItemCount(); i++)
		{
			__int64 Address = StrToInt64(lvDevices.GetItemText(i, 0));
			tstring DevName;
			int Res = Radio->GetRemoteName(Address, DevName);
			if (Res != WCL_E_SUCCESS)
				lvDevices.SetItemText(i, 1, _T("Error: 0x") + IntToHex(Res));
			else
				lvDevices.SetItemText(i, 1, DevName.c_str());
		}
	}	
	TraceEvent(0, _T("Discovering completed"), _T(""), _T(""));
}

void CGattClientDlg::wclBluetoothManagerDiscoveringStarted(void* Sender, CwclBluetoothRadio* const Radio)
{
	UNREFERENCED_PARAMETER(Sender);
	UNREFERENCED_PARAMETER(Radio);

	lvDevices.DeleteAllItems();
	TraceEvent(0, _T("Discovering started"), _T(""), _T(""));
}

void CGattClientDlg::wclGattClientCharacteristicChanged(void* Sender, const unsigned short Handle,
	const unsigned char* const Value, const unsigned long Length)
{
	TraceEvent(((CwclGattClient*)Sender)->Address, _T("ValueChanged"), _T("Handle"), IntToHex(Handle));
	if (Value == NULL || Length == 0)
		TraceEvent(0, _T(""), _T("Value"), _T(""));
	else
	{
		CString Str = _T("");
		for (unsigned long i = 0; i < Length; i++)
			Str = Str + IntToHex(Value[i]);
		
		TraceEvent(0, _T(""), _T("Value"), Str);
	}
}

void CGattClientDlg::wclGattClientConnect(void* Sender, const int Error)
{
	// Connection property is valid here.
	TraceEvent(((CwclGattClient*)Sender)->Address, _T("Connected"), _T("Error"),
		_T("0x") + IntToHex(Error));

	if (Error == WCL_E_SUCCESS)
	{
		TraceEvent(wclGattClient.Address, _T("Max PDU"), _T(""), _T(""));
		GetMaxPduSize();

		TraceEvent(wclGattClient.Address, _T("Connection params"), _T(""), _T(""));
		GetConnectionParams();

		TraceEvent(wclGattClient.Address, _T("Connection PHY"), _T(""), _T(""));
		GetConnectionPhy();
	}
}

void CGattClientDlg::wclGattClientDisconnect(void* Sender, const int Reason)
{
	// Connection property is valid here.
	TraceEvent(((CwclGattClient*)Sender)->Address, _T("Disconnected"), _T("Reason"),
		_T("0x") + IntToHex(Reason));
	
	Cleanup();
}

void CGattClientDlg::OnBnClickedButtonTerminate()
{
	CwclBluetoothRadio* Radio = GetRadio();
	if (Radio != NULL)
	{
		int Res = Radio->Terminate();
		if (Res != WCL_E_SUCCESS)
			AfxMessageBox(_T("Error: 0x") + IntToHex(Res));
	}
}

wclGattProtectionLevel CGattClientDlg::Protection() const
{
	switch (cbProtection.GetCurSel())
	{
		case 0:
			return plNone;
		case 1:
			return plAuthentication;
		case 2:
			return plEncryption;
		case 3:
			return plEncryptionAndAuthentication;
		default:
			return plNone;
	}
}

void CGattClientDlg::wclBluetoothManagerConfirm(void* Sender, CwclBluetoothRadio* const Radio,
	const __int64 Address, bool& Confirm)
{
	UNREFERENCED_PARAMETER(Radio);
	UNREFERENCED_PARAMETER(Sender);

	  // Accept any pairing.
	Confirm = true;
	TraceEvent(Address, _T("Just works pairing"), _T("Accept"), _T("True"));
}

void CGattClientDlg::wclBluetoothManagerAuthenticationCompleted(void* Sender,
	CwclBluetoothRadio* const Radio, const __int64 Address, const int Error)
{
	UNREFERENCED_PARAMETER(Radio);
	UNREFERENCED_PARAMETER(Sender);

	TraceEvent(Address, _T("Authentication completed"), _T("Error"), IntToHex(Error));
}

void CGattClientDlg::OnBnClickedButtonPair()
{
	POSITION Pos = lvDevices.GetFirstSelectedItemPosition();
	if (Pos == NULL)
		AfxMessageBox(_T("Select device"));
	else
	{
		int Item = lvDevices.GetNextSelectedItem(Pos);
		int Res = ((CwclBluetoothRadio*)lvDevices.GetItemData(Item))->RemotePair(StrToInt64(lvDevices.GetItemText(Item, 0)),
			pmLe);
		if (Res != WCL_E_SUCCESS)
			AfxMessageBox(_T("Error: 0x") + IntToHex(Res));
	}
}

void CGattClientDlg::OnBnClickedCheckFastSubscribe()
{
	btCCCDSubscribe.EnableWindow(!cbFastSubscribe.GetCheck());
	btCCCDUnsubscribe.EnableWindow(!cbFastSubscribe.GetCheck());
}

void CGattClientDlg::OnBnClickedButtonGetParams()
{
	TraceEvent(wclGattClient.Address, _T("Connection params"), _T(""), _T(""));
	GetConnectionParams();
}

void CGattClientDlg::wclGattClientConnectionParamsChanged(void* Sender)
{
	UNREFERENCED_PARAMETER(Sender);

	TraceEvent(wclGattClient.Address, _T("Connection params changed"), _T(""), _T(""));
	GetConnectionParams();
}

void CGattClientDlg::OnBnClickedButtonSetParams()
{
	int Res;
	if (cbParams.GetCurSel() == 3)
	{
		wclBluetoothLeConnectionParametersValue ParamsValue = { 84, 84, 0, 800 };
		Res = wclGattClient.SetConnectionParams(ParamsValue);
	}
	else
	{
		wclBluetoothLeConnectionParametersType Params;
		switch (cbParams.GetCurSel())
		{
			case 0:
				Params = ppBalanced;
				break;
			case 1:
				Params = ppPowerOptimized;
				break;
			case 2:
				Params = ppThroughputOptimized;
				break;
			default:
				throw new wclEInvalidArgument("Invalid connection parameters.");
		}
		Res = wclGattClient.SetConnectionParams(Params);
	}
	if (Res != WCL_E_SUCCESS)
		AfxMessageBox(_T("Error: 0x") + IntToHex(Res));
}

void CGattClientDlg::wclGattClientMaxPduSizeChanged(void* Sender)
{
	UNREFERENCED_PARAMETER(Sender);

	TraceEvent(wclGattClient.Address, _T("Max PDU size changed"), _T(""), _T(""));
	GetMaxPduSize();
}

void CGattClientDlg::OnBnClickedButtonGetMaxPduSize()
{
	TraceEvent(wclGattClient.Address, _T("Max PDU"), _T(""), _T(""));
	GetMaxPduSize();
}

void CGattClientDlg::GetMaxPduSize()
{
	unsigned short Size;
	int Res = wclGattClient.GetMaxPduSize(Size);
	if (Res != WCL_E_SUCCESS)
		TraceEvent(wclGattClient.Address, _T(""), _T("Error"), IntToHex(Res));
	else
		TraceEvent(wclGattClient.Address, _T(""), _T("Size"), IntToStr(Size));
}

void CGattClientDlg::GetConnectionParams()
{
	wclBluetoothLeConnectionParameters Params;
	int Res = wclGattClient.GetConnectionParams(Params);
	if (Res != WCL_E_SUCCESS)
		TraceEvent(0, _T(""), _T("Error"), IntToHex(Res));
	else
	{
		TraceEvent(0, _T(""), _T("Connection interval"), IntToStr(Params.Interval));
		TraceEvent(0, _T(""), _T("Connection latency"), IntToStr(Params.Latency));
		TraceEvent(0, _T(""), _T("Link timeout"), IntToStr(Params.LinkTimeout));
	}
}

void CGattClientDlg::wclBluetoothManagerProtectionLevelRequest(void* Sender,
	CwclBluetoothRadio* const Radio, const __int64 Address,
	wclBluetoothLeProtectionLevel& Protection)
{
	UNREFERENCED_PARAMETER(Sender);
	UNREFERENCED_PARAMETER(Radio);
	UNREFERENCED_PARAMETER(Address);

	switch (cbProtection.GetCurSel())
	{
		case 0:
			Protection = pplNone;
			break;
		case 1:
			Protection = pplDefault;
			break;
		case 2:
			Protection = pplEncryption;
			break;
		case 3:
			Protection = pplEncryptionAndAuthentication;
			break;
		default:
			Protection = pplDefault;
			break;
	}
}
void CGattClientDlg::OnBnClickedButtonPhy()
{
	TraceEvent(wclGattClient.Address, _T("Connection PHY"), _T(""), _T(""));
	GetConnectionPhy();
}

void CGattClientDlg::wclGattClientConnectionPhyChanged(void* Sender)
{
	UNREFERENCED_PARAMETER(Sender);

	TraceEvent(wclGattClient.Address, _T("Connection PHY changed"), _T(""), _T(""));
	GetConnectionPhy();
}

void CGattClientDlg::GetConnectionPhy()
{
	wclBluetoothLeConnectionPhy Phy;
	int Res = wclGattClient.GetConnectionPhyInfo(Phy);
	if (Res != WCL_E_SUCCESS)
		TraceEvent(0, _T(""), _T("Error"), IntToHex(Res));
	else
	{
		TraceEvent(0, _T("Transmit PHY"), _T(""), _T(""));
		TraceEvent(0, _T(""), _T("Coded"), BoolToStr(Phy.Transmit.IsCoded));
		TraceEvent(0, _T(""), _T("1M"), BoolToStr(Phy.Transmit.IsUncoded1MPhy));
		TraceEvent(0, _T(""), _T("2M"), BoolToStr(Phy.Transmit.IsUncoded2MPhy));
		
		TraceEvent(0, _T("Receive PHY"), _T(""), _T(""));
		TraceEvent(0, _T(""), _T("Coded"), BoolToStr(Phy.Receive.IsCoded));
		TraceEvent(0, _T(""), _T("1M"), BoolToStr(Phy.Receive.IsUncoded1MPhy));
		TraceEvent(0, _T(""), _T("2M"), BoolToStr(Phy.Receive.IsUncoded2MPhy));
	}
}

void CGattClientDlg::wclBluetoothManagerIoCapabilityRequest(void* Sender,
	CwclBluetoothRadio* const Radio, const __int64 Address, wclBluetoothMitmProtection& Mitm,
	wclBluetoothIoCapability& IoCapability, bool& OobPresent)
{
	UNREFERENCED_PARAMETER(Sender);
	UNREFERENCED_PARAMETER(Radio);
	UNREFERENCED_PARAMETER(Address);

	switch (cbMitm.GetCurSel())
	{
		case 0:
			Mitm = mitmProtectionNotRequired;
			break;
		case 1:
			Mitm = mitmProtectionRequired;
			break;
		case 2:
			Mitm = mitmProtectionNotRequiredBonding;
			break;
		case 3:
			Mitm = mitmProtectionRequiredBonding;
			break;
		case 4:
			Mitm = mitmProtectionNotRequiredGeneralBonding;
			break;
		case 5:
			Mitm = mitmProtectionRequiredGeneralBonding;
			break;
		default:
			Mitm = mitmProtectionNotDefined;
			break;
	}
	
	switch (cbIoCap.GetCurSel())
	{
		case 0:
			IoCapability = iocapDisplayOnly;
			break;
		case 1:
			IoCapability = iocapDisplayYesNo;
			break;
		case 2:
			IoCapability = iocapKeyboardOnly;
			break;
		case 3:
			IoCapability = iocapNoInputNoOutput;
			break;
		case 4:
			IoCapability = iocapDisplayKeyboard;
			break;
		default:
			IoCapability = iocapNotDefined;
			break;
	}
	
	OobPresent = false;
}

void CGattClientDlg::OnBnClickedButtonUnpair()
{
	POSITION Pos = lvDevices.GetFirstSelectedItemPosition();
	if (Pos == NULL)
		AfxMessageBox(_T("Select device"));
	else
	{
		int Item = lvDevices.GetNextSelectedItem(Pos);
		int Res = ((CwclBluetoothRadio*)lvDevices.GetItemData(Item))->RemoteUnpair(StrToInt64(lvDevices.GetItemText(Item, 0)),
			pmLe);
		if (Res != WCL_E_SUCCESS)
			AfxMessageBox(_T("Error: 0x") + IntToHex(Res));
	}
}

void CGattClientDlg::OnBnClickedButtonEnumPaired()
{
	CwclBluetoothRadio* Radio = GetRadio();
	if (Radio != NULL)
	{
		lvDevices.DeleteAllItems();

		wclBluetoothAddresses Devices;
		int Res = Radio->EnumPairedDevices(dkBle, Devices);
		if (Res != WCL_E_SUCCESS)
			AfxMessageBox(_T("Enum paired failed: 0x:") + IntToHex(Res));
		else
		{
			if (Devices.size() > 0)
			{
				for (wclBluetoothAddresses::iterator Device = Devices.begin(); Device != Devices.end(); Device++)
				{
					int Item = lvDevices.GetItemCount();
					lvDevices.InsertItem(Item, IntToHex(*Device));
					lvDevices.SetItemData(Item, (DWORD_PTR)Radio); 

					tstring Name;
					Res = Radio->GetRemoteName(*Device, Name);
					if (Res == WCL_E_SUCCESS)
						lvDevices.SetItemText(Item, 1, Name.c_str());
					else
						lvDevices.SetItemText(Item, 1, _T("Error: 0x") + IntToHex(Res));

					wclBluetoothDeviceType DevType = dtMixed;
					Res = Radio->GetRemoteDeviceType(*Device, DevType);
					if (Res != WCL_E_SUCCESS)
						lvDevices.SetItemText(Item, 2, _T("Error: 0x") + IntToHex(Res));
					else
					{
						switch (DevType)
						{
							case dtClassic:
								lvDevices.SetItemText(Item, 2, _T("Classic"));
								break;
							case dtBle:
								lvDevices.SetItemText(Item, 2, _T("BLE"));
								break;
							case dtMixed:
								lvDevices.SetItemText(Item, 2, _T("Mixed"));
								break;
							default:
								lvDevices.SetItemText(Item, 2, _T("Unknown"));
						}
					}
					
					bool Paired = false;
					Res = Radio->GetRemotePaired(*Device, Paired);
					if (Res != WCL_E_SUCCESS)
						lvDevices.SetItemText(Item, 3, _T("Error: 0x") + IntToHex(Res));
					else
					{
						if (Paired)
							lvDevices.SetItemText(Item, 3, _T("Paired"));
						else
							lvDevices.SetItemText(Item, 3, _T("Unpaired"));
					}
				}
			}
		}
	}
}
