// GattServerDlg.cpp : implementation file
//

#include "stdafx.h"
#include "GattServer.h"
#include "GattServerDlg.h"

#ifdef _DEBUG
#define new DEBUG_NEW
#endif

#define SHORT_DATA_LEN	5
#define LONG_DATA_LEN	1024

BYTE LongData[LONG_DATA_LEN];
BYTE ShortData[SHORT_DATA_LEN];

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

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

CString IntToHex(const unsigned char i)
{
	CString s;
	s.Format(_T("%.2X"), 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;
}

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

CString BoolToStr(const bool b)
{
	if (b)
		return _T("true");
	return _T("false");
}


// CGattServerDlg dialog

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

void CGattServerDlg::DoDataExchange(CDataExchange* pDX)
{
	CDialog::DoDataExchange(pDX);
	DDX_Control(pDX, IDC_BUTTON_START, btStart);
	DDX_Control(pDX, IDC_BUTTON_STOP, btStop);
	DDX_Control(pDX, IDC_BUTTON_DISCONNECT, btDisconnect);
	DDX_Control(pDX, IDC_LIST_DEVICES, lvDevices);
	DDX_Control(pDX, IDC_EDIT_DATA, edData);
	DDX_Control(pDX, IDC_EDIT_REPEATS, edRepeats);
	DDX_Control(pDX, IDC_BUTTON_NPTIFY, btNotify);
	DDX_Control(pDX, IDC_BUTTON_NOTIFY_ALL, btNotifyAll);
	DDX_Control(pDX, IDC_COMBO_PARAMETERS, cbParameters);
	DDX_Control(pDX, IDC_BUTTON_SET_PARAMETERS, btSetParameters);
	DDX_Control(pDX, IDC_LIST_LOG, lbLog);
}

BEGIN_MESSAGE_MAP(CGattServerDlg, CDialog)
	ON_WM_PAINT()
	ON_WM_QUERYDRAGICON()
	//}}AFX_MSG_MAP
	ON_BN_CLICKED(IDC_BUTTON_CLEAR, &CGattServerDlg::OnBnClickedButtonClear)
	ON_WM_DESTROY()
	ON_BN_CLICKED(IDC_BUTTON_START, &CGattServerDlg::OnBnClickedButtonStart)
	ON_BN_CLICKED(IDC_BUTTON_STOP, &CGattServerDlg::OnBnClickedButtonStop)
	ON_NOTIFY(LVN_ITEMCHANGED, IDC_LIST_DEVICES, &CGattServerDlg::OnLvnItemchangedListDevices)
	ON_BN_CLICKED(IDC_BUTTON_DISCONNECT, &CGattServerDlg::OnBnClickedButtonDisconnect)
	ON_BN_CLICKED(IDC_BUTTON_SET_PARAMETERS, &CGattServerDlg::OnBnClickedButtonSetParameters)
	ON_BN_CLICKED(IDC_BUTTON_NOTIFY_ALL, &CGattServerDlg::OnBnClickedButtonNotifyAll)
	ON_BN_CLICKED(IDC_BUTTON_NPTIFY, &CGattServerDlg::OnBnClickedButtonNptify)
END_MESSAGE_MAP()


// CGattServerDlg message handlers

BOOL CGattServerDlg::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

	for (WORD i = 0; i < SHORT_DATA_LEN; i++)
		ShortData[i] = LOBYTE(i + 1);
	for (WORD i = 0; i < LONG_DATA_LEN; i++)
		LongData[i] = LOBYTE(i);

	lvDevices.InsertColumn(0, _T("Client"), 0, 100);
	lvDevices.InsertColumn(1, _T("Subscribed characteristics"), 0, 140);
	lvDevices.InsertColumn(2, _T("Max PDU"), 0, 60);
	lvDevices.InsertColumn(3, _T("Interval"), 0, 50);
	lvDevices.InsertColumn(4, _T("Latency"), 0, 50);
	lvDevices.InsertColumn(5, _T("Timeout"), 0, 50);
	lvDevices.InsertColumn(6, _T("RX coded"), 0, 60);
	lvDevices.InsertColumn(7, _T("RX 1M"), 0, 50);
	lvDevices.InsertColumn(8, _T("RX 2M"), 0, 50);
	lvDevices.InsertColumn(9, _T("TX coded"), 0, 60);
	lvDevices.InsertColumn(10, _T("TX 1M"), 0, 50);
	lvDevices.InsertColumn(11, _T("TX 2M"), 0, 50);
	lvDevices.InsertColumn(12, _T("Max notification"), 0, 90);
	lvDevices.SetExtendedStyle(LVS_EX_FULLROWSELECT);

	cbParameters.AddString(_T("Balanced"));
	cbParameters.AddString(_T("PowerOptimized"));
	cbParameters.AddString(_T("ThroughputOptimized"));
	cbParameters.AddString(_T("Custom"));

	cbParameters.SetCurSel(0);

	edData.SetWindowText(_T("10FF20FF"));

	edRepeats.SetWindowText(_T("10"));

	btStop.EnableWindow(FALSE);
	btDisconnect.EnableWindow(FALSE);
	btNotify.EnableWindow(FALSE);
	btNotifyAll.EnableWindow(FALSE);
	btSetParameters.EnableWindow(FALSE);
	
	for (int i = 0; i < SHORT_DATA_LEN; i++)
		ShortData[i] = LOBYTE(LOWORD(i + 1));
	for (int i = 0; i < LONG_DATA_LEN; i++)
		LongData[i] = LOBYTE(LOWORD(i));

	__hook(&CwclBluetoothManager::AfterOpen, &FManager, &CGattServerDlg::BluetoothManagerAfterOpen);
	__hook(&CwclBluetoothManager::BeforeClose, &FManager, &CGattServerDlg::BluetoothManagerBeforeClose);
	__hook(&CwclBluetoothManager::OnClosed, &FManager, &CGattServerDlg::BluetoothManagerClosed);

	__hook(&CwclGattServer::OnStarted, &FServer, &CGattServerDlg::GattServerStarted);
	__hook(&CwclGattServer::OnStopped, &FServer, &CGattServerDlg::GattServerStopped);
	__hook(&CwclGattServer::OnClientConnected, &FServer, &CGattServerDlg::GattServerClientConnected);
	__hook(&CwclGattServer::OnClientDisconnected, &FServer, &CGattServerDlg::GattServerClientDisconnected);
	__hook(&CwclGattServer::OnMaxPduSizeChanged, &FServer, &CGattServerDlg::GattServerMaxPduSizeChanged);
	__hook(&CwclGattServer::OnConnectionParamsChanged, &FServer, &CGattServerDlg::GattServerConnectionParamsChanged);
	__hook(&CwclGattServer::OnConnectionPhyChanged, &FServer, &CGattServerDlg::GattServerConnectionPhyChanged);
	__hook(&CwclGattServer::OnNotificationSizeChanged, &FServer, &CGattServerDlg::GattServerNotificationSizeChanged);
	__hook(&CwclGattServer::OnSubscribed, &FServer, &CGattServerDlg::GattServerSubscribed);
	__hook(&CwclGattServer::OnUnsubscribed, &FServer, &CGattServerDlg::GattServerUnsubscribed);
	__hook(&CwclGattServer::OnWrite, &FServer, &CGattServerDlg::GattServerWrite);
	__hook(&CwclGattServer::OnRead, &FServer, &CGattServerDlg::GattServerRead);

	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 CGattServerDlg::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 CGattServerDlg::OnQueryDragIcon()
{
	return static_cast<HCURSOR>(m_hIcon);
}

void CGattServerDlg::Trace(const CString& Msg)
{
	lbLog.AddString(Msg);
	lbLog.SetTopIndex(lbLog.GetCount() - 1);
}

void CGattServerDlg::Trace(const BYTE Spaces, const CString& Msg)
{
	CString Str = _T("");
	if (Spaces > 0)
		Str += _T(" ");
	Str += Msg;
	Trace(Str);
}

void CGattServerDlg::Trace(const CString& Msg, const int Error)
{
	Trace(Msg + _T(" failed: 0x") + IntToHex(Error));
}

void CGattServerDlg::Trace(const BYTE Spaces, const CString& Msg,
	const int Error)
{
	Trace(Spaces, Msg + _T(" failed: 0x") + IntToHex(Error));
}

void CGattServerDlg::BluetoothManagerAfterOpen(void* Sender)
{
	UNREFERENCED_PARAMETER(Sender);

	Trace(_T("Bluetooth Manager opened"));
}

void CGattServerDlg::BluetoothManagerBeforeClose(void* Sender)
{
	UNREFERENCED_PARAMETER(Sender);

	Trace(_T("Bluetooth Manager is closing"));
}

void CGattServerDlg::BluetoothManagerClosed(void* Sender)
{
	UNREFERENCED_PARAMETER(Sender);

	Trace(_T("Bluetooth Manager closed"));
}

bool CGattServerDlg::InitializeBluetooth(CwclBluetoothRadio*& Radio)
{
	Radio = NULL;

	Trace(_T("Opening Bluetooth Manager"));
	int Res = FManager.Open();
	if (Res != WCL_E_SUCCESS)
	{
		Trace(_T("Open Bluetooth Manager"), Res);
		return false;
	}
	
	Trace(_T("Get working LE Radio"));
	Res = FManager.GetLeRadio(Radio);
	if (Res != WCL_E_SUCCESS)
	{
		Trace(_T("Get working LE Radio"), Res);
		
		Trace(_T("Closing Bluetooth Manager"));
		FManager.Close();
		
		return false;
	}
	
	Trace(_T("Working radio found. Use ") + CString(Radio->ApiName.c_str()) + _T(" radio"));
	return true;
}

void CGattServerDlg::UninitializeBluetooth()
{
	if (FManager.Active)
	{
		Trace(_T("Closing Bluetooth Manager"));
		FManager.Close();
	}
}

bool CGattServerDlg::InitializeServer(CwclBluetoothRadio* const Radio)
{
	Trace(_T("Initializing GATT server"));
	int Res = FServer.Initialize(Radio);
	if (Res != WCL_E_SUCCESS)
	{
		Trace(_T("GATT server initialization"), Res);
		return false;
	}
	
	Trace(_T("GATT server initialized"));
	return true;
}

void CGattServerDlg::UninitializeServer()
{
	if (FServer.Initialized)
	{
		Trace(_T("Uninitializing GATT server"));
		int Res = FServer.Uninitialize();
		if (Res != WCL_E_SUCCESS)
			Trace(_T("GATT server uninitialization"), Res);
	}
}

bool CGattServerDlg::AddCharacteristics(CwclGattLocalService* const Service)
{
	Trace(4, _T("Adding GATT characteristics"));

	wclGattUuid Uuid;
	Uuid.IsShortUuid = true;
	
	wclGattLocalCharacteristicParameters Params;
	Params.Descriptors.clear();
	Params.PresentationFormats.clear();
	
	CwclGattLocalCharacteristic* Char;

	Trace(8, _T("Add empty characteristic"));
	Uuid.ShortUuid = 0xFFF1;
	Params.Props.clear();
	Params.UserDescription = _T("Empty characteristic");
	int Res = Service->AddCharacteristic(Uuid, Params, Char);
	if (Res != WCL_E_SUCCESS)
	{
		Trace(12, _T("Add empty characteristic"), Res);
		return false;
	}
	Trace(12, _T("Empty characteristic added"));
	
	Trace(8, _T("Add short readable characteristic"));
	Uuid.ShortUuid = 0xFFF2;
	Params.Props.insert(cpReadable);
	Params.UserDescription = _T("Short readable characteristic");
	Res = Service->AddCharacteristic(Uuid, Params, Char);
	if (Res != WCL_E_SUCCESS)
	{
		Trace(12, _T("Add short readable characteristic"), Res);
		return false;
	}
	Trace(12, _T("Short readable characteristic added"));
	
	Trace(8, _T("Add long readable characteristic"));
	Uuid.ShortUuid = 0xFFF3;
	Params.Props.clear();
	Params.Props.insert(cpReadable);
	Params.UserDescription = _T("Long readable characteristic");
	Res = Service->AddCharacteristic(Uuid, Params, Char);
	if (Res != WCL_E_SUCCESS)
	{
		Trace(12, _T("Add long readable characteristic: 0x"), Res);
		return false;
	}
	Trace(12, _T("Long readable characteristic added"));
	
	Trace(8, _T("Add writable characteristic"));
	Uuid.ShortUuid = 0xFFF4;
	Params.Props.clear();
	Params.Props.insert(cpWritable);
	Params.Props.insert(cpWritableWithoutResponse);
	Params.UserDescription = _T("Writable characteristic");
	Res = Service->AddCharacteristic(Uuid, Params, Char);
	if (Res != WCL_E_SUCCESS)
	{
		Trace(12, _T("Add writable characteristic"), Res);
		return false;
	}
	Trace(12, _T("Writable characteristic added"));
	
	Trace(8, _T("Add notifiable characteristic"));
	Uuid.ShortUuid = 0xFFF5;
	Params.Props.clear();
	Params.Props.insert(cpNotifiable);
	Params.UserDescription = _T("Notifiable characteristic");
	Res = Service->AddCharacteristic(Uuid, Params, Char);
	if (Res != WCL_E_SUCCESS)
	{
		Trace(12, _T("Add notifiable characteristic"), Res);
		return false;
	}
	Trace(12, _T("Notifiable characteristic added"));
	
	Trace(8, _T("Add indicatable characteristic"));
	Uuid.ShortUuid = 0xFFF6;
	Params.Props.clear();
	Params.Props.insert(cpIndicatable);
	Params.UserDescription = _T("Indicatable characteristic");
	Res = Service->AddCharacteristic(Uuid, Params, Char);
	if (Res != WCL_E_SUCCESS)
	{
		Trace(12, _T("Add indicatable characteristic"), Res);
		return false;
	}
	Trace(12, _T("Indicatable characteristic added"));
	
	Trace(4, _T("All GATT charactertistics added"));
	return true;
}

CwclGattLocalService* CGattServerDlg::AddService()
{
	Trace(_T("Adding GATT service"));

	wclGattUuid Uuid;
	Uuid.IsShortUuid = true;
	Uuid.ShortUuid = 0xFFF0;
	CwclGattLocalService* Result;
	int Res = FServer.AddService(Uuid, Result);
	if (Res != WCL_E_SUCCESS)
	{
		Trace(4, _T("Add GATT service"), Res);
		return NULL;
	}
	
	Trace(_T("GATT service added"));
	return Result;
}

void CGattServerDlg::Start()
{
	CwclBluetoothRadio* Radio = NULL;
	if (InitializeBluetooth(Radio))
	{
		bool BoolRes = InitializeServer(Radio);
		if (BoolRes)
		{
			CwclGattLocalService* Service = AddService();
			if (Service == NULL)
				BoolRes = false;
			else
			{
				BoolRes = AddCharacteristics(Service);
				if (BoolRes)
				{
					Trace(_T("Starting GATT server"));
					int Res = FServer.Start();
					if (Res != WCL_E_SUCCESS)
					{
						Trace(_T("GATT server start"), Res);
						BoolRes = false;
					}
				}
			}
			if (!BoolRes)
				UninitializeServer();
		}
		if (!BoolRes)
			UninitializeBluetooth();
	}
}

void CGattServerDlg::Stop()
{
	Trace(_T("Stopping GATT server"));
	
	int Res = FServer.Stop();
	if (Res != WCL_E_SUCCESS)
		Trace(_T("Stop GATT server"), Res);
	
	UninitializeServer();
	UninitializeBluetooth();
}

int CGattServerDlg::FindItem(CwclGattServerClient* const Client)
{
	if (Client != NULL)
	{
		if (lvDevices.GetItemCount() > 0)
		{
			for (int i = 0; i < lvDevices.GetItemCount(); i++)
			{
				if ((CwclGattServerClient*)lvDevices.GetItemData(i) == Client)
					return i;
			}
		}
	}
	return -1;
}

void CGattServerDlg::UpdateMaxPduSize(CwclGattServerClient* const Client)
{
	int Item = FindItem(Client);
	if (Item > -1)
	{
		unsigned short Size;
		if (Client->GetMaxPduSize(Size) == WCL_E_SUCCESS)
			lvDevices.SetItemText(Item, 2, IntToStr(Size));
		else
			lvDevices.SetItemText(Item, 2, _T("-----"));
	}
}

void CGattServerDlg::UpdateConnectionParams(CwclGattServerClient* const Client)
{
	int Item = FindItem(Client);
	if (Item > -1)
	{
		wclBluetoothLeConnectionParameters Params;
		if (Client->GetConnectionParams(Params) == WCL_E_SUCCESS)
		{
			lvDevices.SetItemText(Item, 3, IntToStr(Params.Interval));
			lvDevices.SetItemText(Item, 4, IntToStr(Params.Latency));
			lvDevices.SetItemText(Item, 5, IntToStr(Params.LinkTimeout));
		}
		else
		{
			lvDevices.SetItemText(Item, 3, _T("-----"));
			lvDevices.SetItemText(Item, 4, _T("-----"));
			lvDevices.SetItemText(Item, 5, _T("-----"));
		}
	}
}

void CGattServerDlg::UpdatePhy(CwclGattServerClient* const Client)
{
	int Item = FindItem(Client);
	if (Item > -1)
	{
		wclBluetoothLeConnectionPhy Phy;
		if (Client->GetConnectionPhyInfo(Phy) == WCL_E_SUCCESS)
		{
			lvDevices.SetItemText(Item, 6, BoolToStr(Phy.Receive.IsCoded));
			lvDevices.SetItemText(Item, 7, BoolToStr(Phy.Receive.IsUncoded1MPhy));
			lvDevices.SetItemText(Item, 8, BoolToStr(Phy.Receive.IsUncoded2MPhy));
			
			lvDevices.SetItemText(Item, 9, BoolToStr(Phy.Transmit.IsCoded));
			lvDevices.SetItemText(Item, 10, BoolToStr(Phy.Transmit.IsUncoded1MPhy));
			lvDevices.SetItemText(Item, 11, BoolToStr(Phy.Transmit.IsUncoded2MPhy));
		}
		else
		{
			lvDevices.SetItemText(Item, 6, _T("-----"));
			lvDevices.SetItemText(Item, 7, _T("-----"));
			lvDevices.SetItemText(Item, 8, _T("-----"));
			lvDevices.SetItemText(Item, 9, _T("-----"));
			lvDevices.SetItemText(Item, 10, _T("-----"));
			lvDevices.SetItemText(Item, 11, _T("-----"));
		}
	}
}

void CGattServerDlg::UpdateNotificationSize(CwclGattServerClient* const Client)
{
	int Item = FindItem(Client);
	if (Item > -1)
	{
		unsigned short Size;
		if (Client->GetMaxNotificationSize(Size) == WCL_E_SUCCESS)
			lvDevices.SetItemText(Item, 12, IntToStr(Size));
		else
			lvDevices.SetItemText(Item, 12, _T("-----"));
	}
}

void CGattServerDlg::UpdateNotifyAllStatus()
{
	bool Found = false;
	
	if (lvDevices.GetItemCount() > 0)
	{
		for (int i = 0; i < lvDevices.GetItemCount(); i++)
		{
			if (lvDevices.GetItemText(i, 0) == _T(""))
			{
				Found = true;
				break;
			}
		}
	}
	
	if (Found)
	{
		POSITION Pos = lvDevices.GetFirstSelectedItemPosition();
		Found = (Pos != NULL);
		if (Found)
		{
			int Item = lvDevices.GetNextSelectedItem(Pos);
			Found = (lvDevices.GetItemText(Item, 0) == _T(""));
		}
	}

	btNotifyAll.EnableWindow(Found);
}

void CGattServerDlg::GattServerStarted(void* Sender)
{
	UNREFERENCED_PARAMETER(Sender);
	
	btStop.EnableWindow(TRUE);
	btStart.EnableWindow(FALSE);
	
	Trace(_T("Server started"));
}

void CGattServerDlg::GattServerStopped(void* Sender)
{
	UNREFERENCED_PARAMETER(Sender);

	btStop.EnableWindow(FALSE);
	btStart.EnableWindow(TRUE);
	
	Trace(_T("Server stopped"));
}

void CGattServerDlg::GattServerClientConnected(void* Sender,
	CwclGattServerClient* const Client)
{
	UNREFERENCED_PARAMETER(Sender);

	Trace(_T("Client connected: ") + IntToHex(Client->Address));

	int Ndx = lvDevices.GetItemCount();
	lvDevices.InsertItem(Ndx, IntToHex(Client->Address));
	lvDevices.SetItemData(Ndx, (DWORD_PTR)Client);
	lvDevices.SetItemText(Ndx, 1, _T(""));
	lvDevices.SetItemText(Ndx, 2, _T(""));
	lvDevices.SetItemText(Ndx, 3, _T(""));
	lvDevices.SetItemText(Ndx, 4, _T(""));
	lvDevices.SetItemText(Ndx, 5, _T(""));
	lvDevices.SetItemText(Ndx, 6, _T(""));
	lvDevices.SetItemText(Ndx, 7, _T(""));
	lvDevices.SetItemText(Ndx, 8, _T(""));
	lvDevices.SetItemText(Ndx, 9, _T(""));
	lvDevices.SetItemText(Ndx, 10, _T(""));
	lvDevices.SetItemText(Ndx, 11, _T(""));
	lvDevices.SetItemText(Ndx, 12, _T(""));
	
	UpdateMaxPduSize(Client);
	UpdateConnectionParams(Client);
	UpdatePhy(Client);
	UpdateNotificationSize(Client);
}

void CGattServerDlg::GattServerClientDisconnected(void* Sender,
	CwclGattServerClient* const Client)
{
	UNREFERENCED_PARAMETER(Sender);

	Trace(_T("Client disconnected: ") + IntToHex(Client->Address));
	
	int Item = FindItem(Client);
	if (Item > -1)
	{
		// Characteristics should already be removed because unsubscribe always
		// called before disconnect.
		lvDevices.DeleteItem(Item);
		
		UpdateNotifyAllStatus();
	}
}

void CGattServerDlg::GattServerMaxPduSizeChanged(void* Sender,
	CwclGattServerClient* const Client)
{
	UNREFERENCED_PARAMETER(Sender);
	
	UpdateMaxPduSize(Client);
	
	Trace(_T("Max PDU size changed: ") + IntToHex(Client->Address));
}

void CGattServerDlg::GattServerConnectionParamsChanged(void* Sender,
	CwclGattServerClient* const Client)
{
	UNREFERENCED_PARAMETER(Sender);

	UpdateConnectionParams(Client);
	
	Trace(_T("Connection parameters changed: ") + IntToHex(Client->Address));
}

void CGattServerDlg::GattServerConnectionPhyChanged(void* Sender,
	CwclGattServerClient* const Client)
{
	UNREFERENCED_PARAMETER(Sender);

	UpdatePhy(Client);
	
	Trace(_T("PHY changed: ") + IntToHex(Client->Address));
}

void CGattServerDlg::GattServerNotificationSizeChanged(void* Sender,
	CwclGattServerClient* const Client)
{
	UNREFERENCED_PARAMETER(Sender);

	UpdateNotificationSize(Client);
	
	Trace(_T("Notification size changed: ") + IntToHex(Client->Address));
}

void CGattServerDlg::GattServerSubscribed(void* Sender, CwclGattServerClient* const Client,
	CwclGattLocalCharacteristic* const Characteristic)
{
	UNREFERENCED_PARAMETER(Sender);

	Trace(_T("Client ") + IntToHex(Client->Address) + _T(" subscribed to"));
	Trace(4, _T("Characteristic: ") + IntToHex(Characteristic->Uuid.ShortUuid));
	
	// Client already connected on this stage.
	int Item = FindItem(Client);
	if (Item > -1)
	{
		Item++;
		lvDevices.InsertItem(Item, _T(""));
		lvDevices.SetItemData(Item, (DWORD_PTR)Characteristic);
		lvDevices.SetItemText(Item, 1, IntToHex(Characteristic->Uuid.ShortUuid));
		lvDevices.SetItemText(Item, 2, _T(""));
		lvDevices.SetItemText(Item, 3, _T(""));
		lvDevices.SetItemText(Item, 4, _T(""));
		lvDevices.SetItemText(Item, 5, _T(""));
		lvDevices.SetItemText(Item, 6, _T(""));
		lvDevices.SetItemText(Item, 7, _T(""));
		lvDevices.SetItemText(Item, 8, _T(""));
		lvDevices.SetItemText(Item, 9, _T(""));
		lvDevices.SetItemText(Item, 10, _T(""));
		lvDevices.SetItemText(Item, 11, _T(""));
		lvDevices.SetItemText(Item, 12, _T(""));
		
		UpdateNotificationSize(Client);
		UpdateNotifyAllStatus();
	}
}

void CGattServerDlg::GattServerUnsubscribed(void* Sender, CwclGattServerClient* const Client,
	CwclGattLocalCharacteristic* const Characteristic)
{
	UNREFERENCED_PARAMETER(Sender);
	
	Trace(_T("Client ") + IntToHex(Client->Address) + _T(" unsubscribed from"));
	Trace(4, _T("Characteristic: ") + IntToHex(Characteristic->Uuid.ShortUuid));
	
	int Item = FindItem(Client);
	if (Item > -1)
	{
		if (Item < lvDevices.GetItemCount() - 1)
		{
			for (int i = Item + 1; i < lvDevices.GetItemCount(); i++)
			{
				if (lvDevices.GetItemText(i, 0) != _T(""))
					break;
				
				if ((CwclGattLocalCharacteristic*)lvDevices.GetItemData(i) == Characteristic)
				{
					lvDevices.DeleteItem(i);
					
					UpdateNotificationSize(Client);
					UpdateNotifyAllStatus();
					
					break;
				}
			}
		}
	}
}

void CGattServerDlg::GattServerWrite(void* Sender, CwclGattServerClient* const Client,
	CwclGattLocalCharacteristic* const Characteristic,
	CwclGattLocalCharacteristicWriteRequest* const Request)
{
	UNREFERENCED_PARAMETER(Sender);

	Trace(_T("Write request received from: ") + IntToHex(Client->Address));
	Trace(4, _T("Characteristic: ") + IntToHex(Characteristic->Uuid.ShortUuid));
	Trace(4, _T("Size: ") + IntToStr(Request->Size));
	Trace(4, _T("Offset: ") + IntToStr(Request->Offset));
	
	CString s = _T("");
	for (unsigned long i = 0; i < Request->Size; i++)
		s = s + IntToHex(((PBYTE)Request->Data)[i]);
	Trace(8, s);
	
	if (Request->WithResponse)
	{
		Trace(4, _T("Sending response"));
		int Res = Request->Respond();
		// If data is incorrect you can answer with error using:
		//         Res = Request->RespondWithError(Error);
		// For possible error code refer to RespondWithError() method description.
		if (Res != WCL_E_SUCCESS)
			Trace(8, _T("Send response"), Res);
		else
			Trace(8, _T("Response sent"));
	}
}

void CGattServerDlg::GattServerRead(void* Sender, CwclGattServerClient* const Client,
	CwclGattLocalCharacteristic* const Characteristic,
	CwclGattLocalCharacteristicReadRequest* const Request)
{
	UNREFERENCED_PARAMETER(Sender);
	
	Trace(_T("Read request received from: ") + IntToHex(Client->Address));
	Trace(4, _T("Characteristic: ") + IntToHex(Characteristic->Uuid.ShortUuid));
	Trace(4, _T("Offset: ") + IntToStr(Request->Offset));
	Trace(4, _T("Buffer size: ") + IntToStr(Request->Size));
	
	int Res;
	if (Characteristic->Uuid.ShortUuid == 0xFFF2)
		Res = Request->Respond(ShortData, SHORT_DATA_LEN);
	else
		Res = Request->Respond(&LongData[Request->Offset], LONG_DATA_LEN - Request->Offset);
	
	if (Res != WCL_E_SUCCESS)
	{
		Trace(8, _T("Set data"), Res);
		Trace(8, _T("Respond with error"));
		// For possible error code refer to RespondWithError() method description.
		Res = Request->RespondWithError(WCL_E_BLUETOOTH_LE_UNLIKELY);
		if (Res != WCL_E_SUCCESS)
			Trace(12, _T("Respond"), Res);
	}
	else
		Trace(8, _T("Data sent"));
}

void CGattServerDlg::Notify(const __int64 Address)
{
  //Characteristic: TwclGattLocalCharacteristic;
  //Res: Integer;
  //Len: Integer;
  //Str: string;
  //DataLen: Integer;
  //Data: array of Byte;
  //i: Integer;
  //Repeats: Integer;
  //Start: Cardinal;
  //_End: Cardinal;
	
	CString Text;
	edData.GetWindowText(Text);
	int Len = Text.GetLength();
	if (Len == 0)
		AfxMessageBox(_T("Data is empty"));
	else
	{
		int Repeats = 1;

		CString RepeatsText;
		edRepeats.GetWindowText(RepeatsText);
		if (RepeatsText != _T(""))
			Repeats = _ttoi(RepeatsText);
		
		if (Repeats > 20)
			AfxMessageBox(_T("Too many notification repeats"));
		else
		{
			POSITION Pos = lvDevices.GetFirstSelectedItemPosition();
			int Item = lvDevices.GetNextSelectedItem(Pos);
			CwclGattLocalCharacteristic* Characteristic = (CwclGattLocalCharacteristic*)lvDevices.GetItemData(Item);
			
			if (Address == 0)
				Trace(_T("Sending notification to all subscribed clients"));
			else
				Trace(_T("Sending notification to ") + IntToHex(Address));
			Trace(4, _T("Characteristic: ") + IntToHex(Characteristic->Uuid.ShortUuid));
			
			if (Len % 2 != 0)
			{
				Text = _T("0") + Text;
				Len++;
			}
			
			int DataLen = Len / 2;
			unsigned char* Data = (unsigned char*)malloc(DataLen);
			if (Data == NULL)
				AfxMessageBox(_T("Memory allocation failed"));
			else
			{
				int i = 0;
				int j = 0;
				while (i < Len)
				{
					unsigned char b = LOBYTE(LOWORD(_tcstol(Text.Mid(i, 2), NULL, 16)));
					Data[j] = b;
					i += 2;
					j++;
				}
				
				for (i = 0; i < Repeats; i++)
				{
					DWORD Start;
					DWORD End;
					int Res;

					if (Address == 0)
					{
						Start = GetTickCount();
						Res = Characteristic->Notify(Data, DataLen);
						End = GetTickCount();
					}
					else
					{
						Start = GetTickCount();
						Res = Characteristic->Notify(Address, Data, DataLen);
						End = GetTickCount();
					}
					
					if (Res != WCL_E_SUCCESS)
						Trace(8, _T("Attempt [") + IntToStr(i + 1) + _T("]"), Res);
					else
					{
						Trace(8, _T("Attempt [") + IntToStr(i + 1) + _T("] took ") +
							IntToStr(End - Start) + _T(" ms"));
					}
				}
				free(Data);

				Trace(_T("Notification procedure completed"));
			}
		}
	}
}

void CGattServerDlg::OnBnClickedButtonClear()
{
	lbLog.ResetContent();
}

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

	Stop();

	__unhook(&FServer);
	__unhook(&FManager);
}

void CGattServerDlg::OnBnClickedButtonStart()
{
	Start();
}

void CGattServerDlg::OnBnClickedButtonStop()
{
	Stop();
}

void CGattServerDlg::OnLvnItemchangedListDevices(NMHDR *pNMHDR, LRESULT *pResult)
{
	UNREFERENCED_PARAMETER(pNMHDR);

	POSITION Pos = lvDevices.GetFirstSelectedItemPosition();
	if (Pos != NULL)
	{
		int Item = lvDevices.GetNextSelectedItem(Pos);
		CString Caption = lvDevices.GetItemText(Item, 0);
		btDisconnect.EnableWindow(Caption != _T(""));
		btNotify.EnableWindow(Caption == _T(""));
		btSetParameters.EnableWindow(Caption != _T(""));
	}
	else
	{
		btDisconnect.EnableWindow(FALSE);
		btNotify.EnableWindow(FALSE);
		btSetParameters.EnableWindow(FALSE);
	}
	
	UpdateNotifyAllStatus();

	*pResult = 0;
}

void CGattServerDlg::OnBnClickedButtonDisconnect()
{
	// Button is disabled if nothing or incorrect line selected.
	POSITION Pos = lvDevices.GetFirstSelectedItemPosition();
	if (Pos != NULL)
	{
		int Item = lvDevices.GetNextSelectedItem(Pos);
		int Res = ((CwclGattServerClient*)lvDevices.GetItemData(Item))->Disconnect();
		if (Res != WCL_E_SUCCESS)
			AfxMessageBox(_T("Disconnect error: 0x") + IntToHex(Res));
	}
}

void CGattServerDlg::OnBnClickedButtonSetParameters()
{
	POSITION Pos = lvDevices.GetFirstSelectedItemPosition();
	if (Pos != NULL)
	{
		int Item = lvDevices.GetNextSelectedItem(Pos);
		CwclGattServerClient* Client = (CwclGattServerClient*)lvDevices.GetItemData(Item);
		
		int Res;

		Trace(_T("Set connection parameters for ") + IntToHex(Client->Address));
		if (cbParameters.GetCurSel() == 3)
		{
			wclBluetoothLeConnectionParametersValue ParamsValue;
			ParamsValue.MinInterval = 84;
			ParamsValue.MaxInterval = 84;
			ParamsValue.Latency = 0;
			ParamsValue.LinkTimeout = 800;
			Res = Client->SetConnectionParams(ParamsValue);
		}
		else
		{
			wclBluetoothLeConnectionParametersType Params;
			switch (cbParameters.GetCurSel())
			{
				case 0:
					Params = ppBalanced;
					break;
				case 1:
					Params = ppPowerOptimized;
					break;
				case 2:
					Params = ppThroughputOptimized;
					break;
				default:
					throw new wclEInvalidArgument("Invalid connection parameters.");
			}
			Res = Client->SetConnectionParams(Params);
		}
		
		if (Res != WCL_E_SUCCESS)
			Trace(4, _T("Set connection parameters"), Res);
		else
			Trace(4, _T("Connection parameters set"));
	}
}

void CGattServerDlg::OnBnClickedButtonNotifyAll()
{
	Notify(0);
}

void CGattServerDlg::OnBnClickedButtonNptify()
{
	POSITION Pos = lvDevices.GetFirstSelectedItemPosition();
	int Item = lvDevices.GetNextSelectedItem(Pos);
	for (int i = Item; i >= 0; i--)
	{
		if (lvDevices.GetItemText(i, 0) != _T(""))
		{
			Notify(((CwclGattServerClient*)lvDevices.GetItemData(i))->Address);
			break;
		}
	}
}

BOOL CGattServerDlg::PreTranslateMessage(MSG* pMsg)
{
	if (pMsg->message == WM_CHAR && (GetFocus() == &edRepeats || GetFocus() == &edData))
	{
		if ((pMsg->wParam >= '0' && pMsg->wParam <= '9') || pMsg->wParam == 8)
			return CDialog::PreTranslateMessage(pMsg);

		if (GetFocus() == &edData)
		{
			if ((pMsg->wParam >= 'a' && pMsg->wParam <= 'f') || (pMsg->wParam >= 'A' && pMsg->wParam <= 'F'))
				return CDialog::PreTranslateMessage(pMsg);
		}
		return TRUE;
	}

	return CDialog::PreTranslateMessage(pMsg);
}
