// GattServerDlg.cpp : implementation file
//

#include "stdafx.h"
#include "GattServer.h"
#include "GattServerDlg.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 __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_LIST_LOG, lbLog);
}

BEGIN_MESSAGE_MAP(CGattServerDlg, CDialog)
	ON_WM_PAINT()
	ON_WM_QUERYDRAGICON()
	//}}AFX_MSG_MAP
	ON_BN_CLICKED(IDC_BUTTON_START, &CGattServerDlg::OnBnClickedButtonStart)
	ON_BN_CLICKED(IDC_BUTTON_STOP, &CGattServerDlg::OnBnClickedButtonStop)
	ON_WM_DESTROY()
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

	__hook(&CwclBluetoothManager::AfterOpen, &FManager, &CGattServerDlg::FManager_AfterOpen);
	__hook(&CwclBluetoothManager::BeforeClose, &FManager, &CGattServerDlg::FManager_BeforeClose);

	__hook(&CwclGattServer::OnStarted, &FServer, &CGattServerDlg::FServer_OnStarted);
	__hook(&CwclGattServer::OnStopped, &FServer, &CGattServerDlg::FServer_OnStopped);
	__hook(&CwclGattServer::OnWrite, &FServer, &CGattServerDlg::FServer_OnWrite);
	__hook(&CwclGattServer::OnRead, &FServer, &CGattServerDlg::FServer_OnRead);
	__hook(&CwclGattServer::OnSubscribed, &FServer, &CGattServerDlg::FServer_OnSubscribed);
	__hook(&CwclGattServer::OnUnsubscribed, &FServer, &CGattServerDlg::FServer_OnUnsubscribed);
	__hook(&CwclGattServer::OnClientConnected, &FServer, &CGattServerDlg::FServer_OnClientConnected);
	__hook(&CwclGattServer::OnClientDisconnected, &FServer, &CGattServerDlg::FServer_OnClientDisconnected);
	__hook(&CwclGattServer::OnNotificationSizeChanged, &FServer, &CGattServerDlg::FServer_OnNotificationSizeChanged);
	__hook(&CwclGattServer::OnMaxPduSizeChanged, &FServer, &CGattServerDlg::FServer_OnMaxPduSizeChanged);
	__hook(&CwclGattServer::OnConnectionParamsChanged, &FServer, &CGattServerDlg::FServer_OnConnectionParamsChanged);
	__hook(&CwclGattServer::OnConnectionPhyChanged, &FServer, &CGattServerDlg::FServer_OnConnectionPhyChanged);
	
	FStarted = false;
	for (WORD i = 0; i < SHORT_DATA_LEN; i++)
		FShortData[i] = LOBYTE(i + 1);
	for (WORD i = 0; i < LONG_DATA_LEN; i++)
		FLongData[i] = LOBYTE(i);

	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::FManager_AfterOpen(void* Sender)
{
	UNREFERENCED_PARAMETER(Sender);

	lbLog.AddString(_T("Bluetooth Manager has been opened"));
}

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

	lbLog.AddString(_T("Bluetooth Manager is closing"));
}

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

	lbLog.AddString(_T("Server has been started"));
	FStarted = true;
	FCounter = 0;
}

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

	lbLog.AddString(_T("Server has been stopped"));
	FStarted = false;
}

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

	lbLog.AddString(_T("Client connected ") + IntToHex(Client->Address));

	unsigned short Size;
	int Res = Client->GetMaxPduSize(Size);
	if (Res != WCL_E_SUCCESS)
		lbLog.AddString(_T("Get max PDU size failed: 0x") + IntToHex(Res));
	else
		lbLog.AddString(_T("Max PDU size: ") + IntToStr(Size));

	GetConParams(Client);
	GetConPhy(Client);
}

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

	GetConParams(Client);
}

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

	lbLog.AddString(_T("Client disconnected ") + IntToHex(Client->Address));
}

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

	lbLog.AddString(_T("Data received from ") + IntToHex(Client->Address));
	lbLog.AddString(_T("  Size: ") + IntToStr(Request->Size));
	lbLog.AddString(_T("  Offset: ") + IntToStr(Request->Offset));

	CString s = _T("");
	for (unsigned long i = 0; i < Request->Size; i++)
		s = s + IntToHex(((BYTE*)Request->Data)[i]);
	lbLog.AddString(s);

	if (Request->WithResponse)
	{
		lbLog.AddString(_T("  Write With Response. Sending response"));
		int Res = Request->Respond();
		if (Res != WCL_E_SUCCESS)
			lbLog.AddString(_T("  Respond failed: 0x") + IntToHex(Res));
	}
}

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

	lbLog.AddString(_T("Read request from ") + IntToHex(Client->Address));
	lbLog.AddString(_T("  Offset: ") + IntToStr(Request->Offset));
	lbLog.AddString(_T("  Read Buffer Size: ") + IntToStr(Request->Size));

	int Res;
	bool IsShort = (Characteristic->Uuid.IsShortUuid &&
		Characteristic->Uuid.ShortUuid == 0xFFF2);
	if (IsShort)
		Res = Request->Respond(FShortData, SHORT_DATA_LEN);
	else
	{
		Res = Request->Respond(&FLongData[Request->Offset],
			LONG_DATA_LEN - Request->Offset);
	}
	if (Res != WCL_E_SUCCESS)
	{
		lbLog.AddString(_T("  Set data failed: 0x") + IntToHex(Res));
		lbLog.AddString(_T("  respond with error"));
		Res = Request->RespondWithError(WCL_E_BLUETOOTH_LE_UNLIKELY);
		if (Res != WCL_E_SUCCESS)
			lbLog.AddString(_T("  Respond failed: 0x") + IntToHex(Res));
	}
}

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

	CString Str = _T("Client subscribed: ") + IntToHex(Client->Address);
	
	unsigned short Size;
	int Res = Client->GetMaxNotificationSize(Size);
	if (Res != WCL_E_SUCCESS)
		Str = Str + _T(" Get max notification size failed: 0x") + IntToHex(Res);
	else
		Str = Str + _T(" Max notification size: ") + IntToStr(Size);
	lbLog.AddString(Str);

	Res = Characteristic->Notify(Client->Address, (unsigned char*)&FCounter, 4);
	FCounter++;
	if (Res != WCL_E_SUCCESS)
		lbLog.AddString(_T("Notification failed: ") + IntToHex(Res));
}

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

	unsigned short Size;
	int Res = Client->GetMaxNotificationSize(Size);
	if (Res == WCL_E_SUCCESS)
	{
		lbLog.AddString(IntToHex(Client->Address) +
			_T(" notification size changed: ") + IntToStr(Size));
	}
	else
	{
		lbLog.AddString(IntToHex(Client->Address) +
			_T(" get max notification size failed: 0x") + IntToHex(Res));
	}
}

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

	lbLog.AddString(_T("Client unsubscribed :") + IntToHex(Client->Address));
}

bool CGattServerDlg::InitBluetooth(CwclBluetoothRadio*& Radio)
{
	Radio = NULL;
	
	bool Result = false;
	
	int Res = FManager.Open();
	if (Res != WCL_E_SUCCESS)
		lbLog.AddString(_T("Bluetooth Manager open failed: 0x") + IntToHex(Res));
	else
	{
		lbLog.AddString(_T("Checking for working Radio"));
		Res = FManager.GetLeRadio(Radio);
		if (Res != WCL_E_SUCCESS)
			lbLog.AddString(_T("Get workign radio failed: 0x") + IntToHex(Res));
		else
		{
			lbLog.AddString(_T("Use ") + CString(Radio->ApiName.c_str()) + _T(" radio"));
			Result = true;
		}
		
		if (!Result)
			FManager.Close();
	}
	
	return Result;
}

void CGattServerDlg::UninitBluetooth()
{
	lbLog.AddString(_T("Close Bluetooth manager"));
	if (FManager.Active)
		FManager.Close();
}

bool CGattServerDlg::InitServer(CwclBluetoothRadio* const Radio)
{
	lbLog.AddString(_T("Initialize server"));
	int Res = FServer.Initialize(Radio);
	if (Res != WCL_E_SUCCESS)
	{
		lbLog.AddString(_T("Initialize server failed: 0x") + IntToHex(Res));
		return false;
	}
	
	lbLog.AddString(_T("Server initialized"));
	return true;
}

void CGattServerDlg::UninitServer()
{
	lbLog.AddString(_T("Uninitializing server"));
	int Res = FServer.Uninitialize();
	if (Res != WCL_E_SUCCESS)
		lbLog.AddString(_T("Uninitialize server failed: 0x") + IntToHex(Res));
}

bool CGattServerDlg::AddCharacteristics(CwclGattLocalService* const Service)
{
	lbLog.AddString(_T("Adding characteristics services"));
	wclGattUuid Uuid;
	Uuid.IsShortUuid = true;
	
	wclGattLocalCharacteristicParameters Params;
	CwclGattLocalCharacteristic* Char;

	Params.Descriptors.clear();
	Params.PresentationFormats.clear();
	
	lbLog.AddString(_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)
	{
		lbLog.AddString(_T("Failed to add empty characteristic: 0x") + IntToHex(Res));
		return false;
	}
	
	lbLog.AddString(_T("Add short readable characteristic"));
	Uuid.ShortUuid = 0xFFF2;
	Params.Props.clear();
	Params.Props.insert(cpReadable);
	Params.UserDescription = _T("Short readable characteristic");
	Res = Service->AddCharacteristic(Uuid, Params, Char);
	if (Res != WCL_E_SUCCESS)
	{
		lbLog.AddString(_T("Failed to add short readable characteristic: 0x") + IntToHex(Res));
		return false;
	}

	lbLog.AddString(_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)
	{
		lbLog.AddString(_T("Failed to add long readable characteristic: 0x") + IntToHex(Res));
		return false;
	}
	
	lbLog.AddString(_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)
	{
		lbLog.AddString(_T("Failed to add writable characteristic: 0x") + IntToHex(Res));
		return false;
	}
	
	lbLog.AddString(_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)
	{
		lbLog.AddString(_T("Failed to add notifiable characteristic: 0x") + IntToHex(Res));
		return false;
	}
	
	lbLog.AddString(_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)
	{
		lbLog.AddString(_T("Failed to add indicatable characteristic: 0x") + IntToHex(Res));
		return false;
	}
	
	lbLog.AddString(_T("All charactertistics were added"));
	return true;
}

bool CGattServerDlg::AddServices()
{
	lbLog.AddString(_T("Preparing services"));
	wclGattUuid Uuid;
	Uuid.IsShortUuid = true;
	Uuid.ShortUuid = 0xFFF0;
	
	lbLog.AddString(_T("Add service"));
	CwclGattLocalService* Service;
	int Res = FServer.AddService(Uuid, Service);
	if (Res != WCL_E_SUCCESS)
	{
		lbLog.AddString(_T("Failed to add service: 0x") + IntToHex(Res));
		return false;
	}
	
	lbLog.AddString(_T("Service has been added"));
	return AddCharacteristics(Service);
}

void CGattServerDlg::Start()
{
	if (Started)
		lbLog.AddString(_T("Already started"));
	else
	{
		CwclBluetoothRadio* Radio = NULL;
		if (InitBluetooth(Radio))
		{
			bool Result = InitServer(Radio);
			if (Result)
			{
				Result = AddServices();
				if (Result)
				{
					lbLog.AddString(_T("Starting server"));
					int Res = FServer.Start();
					if (Res != WCL_E_SUCCESS)
					{
						lbLog.AddString(_T("Start server failed: 0x") + IntToHex(Res));
						Result = false;
					}
				}
				if (!Result)
					UninitServer();
			}
			if (!Result)
				UninitBluetooth();
		}
	}
}

void CGattServerDlg::Stop()
{
	if (!Started)
		lbLog.AddString(_T("Not started"));
	else
	{
		lbLog.AddString(_T("Stopping server"));
		int Res = FServer.Stop();
		if (Res != WCL_E_SUCCESS)
			lbLog.AddString(_T("Stop server failed: 0x") + IntToHex(Res));
		
		UninitServer();
		UninitBluetooth();
	}
}

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

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

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

	Stop();

	// 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(&FManager);
	__unhook(&FServer);
}

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

	unsigned short Size;
	int Res = Client->GetMaxPduSize(Size);
	if (Res == WCL_E_SUCCESS)
		lbLog.AddString(IntToHex(Client->Address) + _T(" PDU size changed: ") + IntToStr(Size));
	else
		lbLog.AddString(IntToHex(Client->Address) + _T(" get max PDU size failed: 0x") + IntToHex(Res));
}

void CGattServerDlg::GetConParams(CwclGattServerClient* const Client)
{
	wclBluetoothLeConnectionParameters Params;
	int Res = Client->GetConnectionParams(Params);
	if (Res != WCL_E_SUCCESS)
		lbLog.AddString(_T("Get connection params failed: 0x") + IntToHex(Res));
	else
	{
		lbLog.AddString(_T("Connection params [") + IntToHex(Client->Address) + _T("]"));
		lbLog.AddString(_T("  Interval     : ") + IntToStr(Params.Interval));
		lbLog.AddString(_T("  Latency      : ") + IntToStr(Params.Latency));
		lbLog.AddString(_T("  Link Timeout : ") + IntToStr(Params.LinkTimeout));
	}
}

void CGattServerDlg::GetConPhy(CwclGattServerClient* const Client)
{
	wclBluetoothLeConnectionPhy Phy;
	int Res = Client->GetConnectionPhyInfo(Phy);
	if (Res != WCL_E_SUCCESS)
		lbLog.AddString(_T("Get connection PHY failed: 0x") + IntToHex(Res));
	else
	{
		lbLog.AddString(_T("Connection PHY [") + IntToHex(Client->Address) + _T("]"));
		lbLog.AddString(_T("  Receive"));
		lbLog.AddString(_T("    IsCoded        : ") + BoolToStr(Phy.Receive.IsCoded));
		lbLog.AddString(_T("    IsUncoded1MPhy : ") + BoolToStr(Phy.Receive.IsUncoded1MPhy));
		lbLog.AddString(_T("    IsUncoded2MPhy : ") + BoolToStr(Phy.Receive.IsUncoded2MPhy));
		lbLog.AddString(_T("  Transmit"));
		lbLog.AddString(_T("    IsCoded        : ") + BoolToStr(Phy.Transmit.IsCoded));
		lbLog.AddString(_T("    IsUncoded1MPhy : ") + BoolToStr(Phy.Transmit.IsUncoded1MPhy));
		lbLog.AddString(_T("    IsUncoded2MPhy : ") + BoolToStr(Phy.Transmit.IsUncoded2MPhy));
	}
}

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

	GetConPhy(Client);
}