// ConsoleApplication1.cpp : Defines the entry point for the console application.
//

#include "pch.h"
#include <process.h>

#include "..\..\..\..\Source\Bluetooth\wclBluetooth.h"

using namespace std;
using namespace wclCommon;
using namespace wclBluetooth;

#define SHORT_DATA_LEN	5
#define LONG_DATA_LEN	1024

class CGattServer
{
private:
	HANDLE FTerminateEvent;
	HANDLE FGattThread;
	DWORD FCounter;
	BYTE FLongData[LONG_DATA_LEN];
	BYTE FShortData[SHORT_DATA_LEN];
	
	void Server_OnStarted(void* sender)
	{
		UNREFERENCED_PARAMETER(sender);

		cout << "Server has been started" << endl;
		FCounter = 0;
	}
	
	void Server_OnStopped(void* sender)
	{
		UNREFERENCED_PARAMETER(sender);

		cout << "Server has been stopped" << endl;
	}

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

		cout << "Data received from " << hex << Client->Address << dec << endl;
		cout << "  Size: " << Request->Size << endl;
		cout << "  Offset: " << Request->Offset << hex << endl;

		for (unsigned long i = 0; i < Request->Size; i++)
		{
			unsigned char b = ((BYTE*)Request->Data)[i];
			cout << (unsigned long)b;
		}
		cout << dec << endl;

		if (Request->WithResponse)
		{
			cout << "  Write With Response. Sending response" << endl;
			int Res = Request->Respond();
			if (Res != WCL_E_SUCCESS)
				cout << "  Respond failed: 0x" << hex << Res << dec << endl;
		}
	}
	
	void Server_OnRead(void* Sender, CwclGattServerClient* const Client,
		CwclGattLocalCharacteristic* const Characteristic,
		CwclGattLocalCharacteristicReadRequest* const Request)
	{
		UNREFERENCED_PARAMETER(Sender);
		UNREFERENCED_PARAMETER(Characteristic);

		cout << "Read request from " << hex << Client->Address << dec << endl;
		cout << "  Offset: " << Request->Offset << endl;
		cout << "  Read Buffer Size: " << Request->Size << endl;

		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)
		{
			cout << "  Set data failed: 0x" << hex << Res << dec << endl;
			cout << "  respond with error" << endl;
			Res = Request->RespondWithError(WCL_E_BLUETOOTH_LE_UNLIKELY);
			if (Res != WCL_E_SUCCESS)
				cout << "  Respond failed: 0x" << hex << Res << dec << endl;
		}
	}

	void Server_OnSubscribed(void* Sender, CwclGattServerClient* const Client,
		CwclGattLocalCharacteristic* const Characteristic)
	{
		UNREFERENCED_PARAMETER(Sender);

		cout << "Client subscribed :" << hex << Client->Address << dec;

		unsigned short Size;
		int Res = Client->GetMaxNotificationSize(Size);
		if (Res != WCL_E_SUCCESS)
			cout << " Get max notification size failed: 0x" << hex << Res << dec << endl;
		else
			cout << " Max notification size: " << Size << endl;

		Res = Characteristic->Notify(Client->Address, (unsigned char*)&FCounter, 4);
		FCounter++;
		if (Res != WCL_E_SUCCESS)
			cout << "Notification failed 0x" << hex << Res << dec << endl;
	}

	void Server_OnNotificationSizeChanged(void* Sender, CwclGattServerClient* const Client)
	{
		UNREFERENCED_PARAMETER(Sender);

		unsigned short Size;
		int Res = Client->GetMaxNotificationSize(Size);
		if (Res == WCL_E_SUCCESS)
			cout << hex << Client->Address << dec << " notification size changed: " << Size << endl;
		else
			cout << hex << Client->Address << " get max notification size failed: 0x" << Res << dec << endl;
	}
	
	void Server_OnMaxPduSizeChanged(void* Sender, CwclGattServerClient* const Client)
	{
		UNREFERENCED_PARAMETER(Sender);

		unsigned short Size;
		int Res = Client->GetMaxPduSize(Size);
		if (Res == WCL_E_SUCCESS)
			cout << hex << Client->Address << dec << " PDU size changed: " << Size << endl;
		else
			cout << hex << Client->Address << " get max PDU size failed: 0x" << Res << dec << endl;
	}
	
	void Server_OnUnsubscribed(void* Sender, CwclGattServerClient* const Client,
		CwclGattLocalCharacteristic* const Characteristic)
	{
		UNREFERENCED_PARAMETER(Sender);
		UNREFERENCED_PARAMETER(Characteristic);

		cout << "Client unsubscribed :" << hex << Client->Address << dec << endl;
	}

	void Server_OnClientConnected(void* Sender, CwclGattServerClient* const Client)
	{
		UNREFERENCED_PARAMETER(Sender);

		cout << "Client connected :" << hex << Client->Address << dec << endl;

		unsigned short Size;
		int Res = Client->GetMaxPduSize(Size);
		if (Res != WCL_E_SUCCESS)
			cout << "Get max PDU size failed: 0x" << hex << Res << dec << endl;
		else
			cout << "Max PDU size: " << Size << endl;

		GetConParams(Client);
		GetConPhy(Client);
	}

	void Server_OnConnectionParamsChanged(void* Sender, CwclGattServerClient* const Client)
	{
		UNREFERENCED_PARAMETER(Sender);

		GetConParams(Client);
	}

	void Server_OnConnectionPhyChanged(void* Sender, CwclGattServerClient* const Client)
	{
		UNREFERENCED_PARAMETER(Sender);

		GetConPhy(Client);
	}

	void Server_OnClientDisconnected(void* Sender, CwclGattServerClient* const Client)
	{
		UNREFERENCED_PARAMETER(Sender);

		cout << "Client disconnected :" << hex << Client->Address << dec << endl;
	}

	static UINT __stdcall _ServerThread(LPVOID Param)
	{
		((CGattServer*)Param)->ServerThread();
		return 0;
	}

	void ServerThread()
	{
		// Create and open Bluetooth Manager.
		CwclBluetoothManager* Manager = new CwclBluetoothManager();
		int Res = Manager->Open();
		if (Res != WCL_E_SUCCESS)
			cout << "Open Bluetooth Manager failed: 0x" << hex << Res << dec << endl;
		else
		{
			if (Manager->Count == 0)
				cout << "No Bluetooth hardware was found" << endl;
			else
			{
				CwclBluetoothRadio* Radio = NULL;
				for (size_t i = 0; i < Manager->Count; i++)
				{
					if (Manager->Radios[i]->Available)
					{
						Radio = Manager->Radios[i];
						break;
					}
				}
				if (Radio == NULL)
					cout << "No available Bluetooth radio found" << endl;
				else
				{
					// Create serve rand set its event handlers.
					CwclGattServer* Server = new CwclGattServer();
					__hook(&CwclGattServer::OnStarted, Server, &CGattServer::Server_OnStarted);
					__hook(&CwclGattServer::OnStopped, Server, &CGattServer::Server_OnStopped);
					__hook(&CwclGattServer::OnWrite, Server, &CGattServer::Server_OnWrite);
					__hook(&CwclGattServer::OnRead, Server, &CGattServer::Server_OnRead);
					__hook(&CwclGattServer::OnSubscribed, Server, &CGattServer::Server_OnSubscribed);
					__hook(&CwclGattServer::OnUnsubscribed, Server, &CGattServer::Server_OnUnsubscribed);
					__hook(&CwclGattServer::OnClientConnected, Server, &CGattServer::Server_OnClientConnected);
					__hook(&CwclGattServer::OnClientDisconnected, Server, &CGattServer::Server_OnClientDisconnected);
					__hook(&CwclGattServer::OnNotificationSizeChanged, Server, &CGattServer::Server_OnNotificationSizeChanged);
					__hook(&CwclGattServer::OnMaxPduSizeChanged, Server, &CGattServer::Server_OnMaxPduSizeChanged);
					__hook(&CwclGattServer::OnConnectionParamsChanged, Server, &CGattServer::Server_OnConnectionParamsChanged);
					__hook(&CwclGattServer::OnConnectionPhyChanged, Server, &CGattServer::Server_OnConnectionPhyChanged);
					
					Res = Server->Initialize(Radio);
					if (Res != WCL_E_SUCCESS)
						cout << "Initialize server failed: 0x" << hex << Res << dec << endl;
					else
					{
						cout << "Server initialized" << endl;

						cout << "Preparing services" << endl;
						wclGattUuid Uuid;
						Uuid.IsShortUuid = true;
						Uuid.ShortUuid = 0xFFF0;

						cout << "Add service" << endl;
						CwclGattLocalService* Service;
						Res = Server->AddService(Uuid, Service);
						if (Res != WCL_E_SUCCESS)
							cout << "Failed to add service: 0x" << hex << Res << dec << endl;
						else
						{
							cout << "Service has been added" << endl;

							cout << "Adding characteristics" << endl;
							Uuid.IsShortUuid = true;

							wclGattLocalCharacteristicParameters Params;
							CwclGattLocalCharacteristic* Char;

							Params.Descriptors.clear();
							Params.PresentationFormats.clear();
							
							cout << "Add empty characteristic" << endl;
							Uuid.ShortUuid = 0xFFF1;
							Params.Props.clear();
							Params.UserDescription  = L"Empty characteristic";
							Res = Service->AddCharacteristic(Uuid, Params, Char);
							if (Res != WCL_E_SUCCESS)
								cout << "Failed to add empty characteristic: 0x" << hex << Res << dec << endl;
							else
							{
								cout << "Add short readable characteristic" << endl;
								Uuid.ShortUuid = 0xFFF2;
								Params.Props.clear();
								Params.Props.insert(cpReadable);
								Params.UserDescription  = L"Short readable characteristic";
								// Add fake presentation format
								wclGattCharacteristicFormat Format;
								Format.AUnit = 0x0101;
								Format.Description = 0x0202;
								Format.Exponent = 0x01;
								Format.Format = fmtUInt32;
								Format.NameSpace = 0x01;
								Params.PresentationFormats.push_back(Format);
								Res = Service->AddCharacteristic(Uuid, Params, Char);
								if (Res != WCL_E_SUCCESS)
									cout << "Failed to add short readable characteristic: 0x" << hex << Res << dec << endl;
								else
								{
									cout << "Add long readable characteristic" << endl;
									Uuid.ShortUuid = 0xFFF3;
									Params.Props.clear();
									Params.Props.insert(cpReadable);
									Params.UserDescription = L"Long readable characteristic";
									Res = Service->AddCharacteristic(Uuid, Params, Char);
									if (Res != WCL_E_SUCCESS)
										cout << "Failed to add long readable characteristic: 0x" << hex << Res << dec << endl;
									else
									{
										cout << "Add writable characteristic" << endl;
										Uuid.ShortUuid = 0xFFF4;
										Params.Props.clear();
										Params.Props.insert(cpWritable);
										Params.Props.insert(cpWritableWithoutResponse);
										Params.UserDescription = L"Writable characteristic";
										// Add second fake presentation format
										Format.AUnit = 0x0303;
										Format.Description = 0x0404;
										Format.Exponent = 0x02;
										Format.Format = fmtUInt48;
										Format.NameSpace = 0x00;
										Params.PresentationFormats.push_back(Format);
										Res = Service->AddCharacteristic(Uuid, Params, Char);
										if (Res != WCL_E_SUCCESS)
											cout << "Failed to add writable characteristic: 0x" << hex << Res << dec << endl;
										else
										{
											cout << "Add notifiable characteristic" << endl;
											Uuid.ShortUuid = 0xFFF5;
											Params.Props.clear();
											Params.Props.insert(cpNotifiable);
											Params.UserDescription = L"Notifiable characteristic";
											Params.PresentationFormats.clear();
											// Add custom descriptor
											unsigned char Data[2] = { 0x01, 0x02 };
											wclGattLocalDescriptor Descriptor;
											Descriptor.Data = &Data[0];
											Descriptor.Size = 2;
											Descriptor.Uuid = 0x2908;
											Descriptor.ReadProtectionLevel = plNone;
											Descriptor.WriteProtectionLevel = plNone;
											Params.Descriptors.push_back(Descriptor);
											Res = Service->AddCharacteristic(Uuid, Params, Char);
											if (Res != WCL_E_SUCCESS)
												cout << "Failed to add notifiable characteristic: 0x" << hex << Res << dec << endl;
											else
											{
												cout << "Add indicatable characteristic" << endl;
												Uuid.ShortUuid = 0xFFF6;
												Params.Props.clear();
												Params.Props.insert(cpIndicatable);
												Params.UserDescription = L"Indicatable characteristic";
												Params.Descriptors.clear();
												Res = Service->AddCharacteristic(Uuid, Params, Char);
												if (Res != WCL_E_SUCCESS)
													cout << "Failed to add indicatable characteristic: 0x" << hex << Res << dec << endl;
												else
												{
													cout << "All charactertistics were added" << endl;

													Res = Server->Start();
													if (Res != WCL_E_SUCCESS)
														cout << "Start server failed: 0x" << hex << Res << dec << endl;
													else
													{
														CwclMessageBroadcaster::Wait(FTerminateEvent);

														Res = Server->Stop();
														if (Res != WCL_E_SUCCESS)
															cout << "Stop server failed: 0x" << hex << Res << dec << endl;
													}
												}
											}
										}
									}
								}
							}
						}

						cout << "Uninitializing server" << endl;
						Res = Server->Uninitialize();
						if (Res != WCL_E_SUCCESS)
							cout << "Uninitialize server failed: 0x" << hex << Res << dec << endl;
					}
					__unhook(Server);
					delete Server;
				}
			}
			Manager->Close();
		}
		delete Manager;
	}

	void GetConParams(CwclGattServerClient* const Client)
	{
		wclBluetoothLeConnectionParameters Params;
		int Res = Client->GetConnectionParams(Params);
		if (Res != WCL_E_SUCCESS)
			cout << "Get connection params failed : 0x" << hex << Res << dec << endl;
		else
		{
			cout << "Connection params[" << hex << Client->Address << dec << "]" << endl;
			cout << "  Interval     : " << Params.Interval << endl;
			cout << "  Latency      : " << Params.Latency << endl;
			cout << "  Link Timeout : " << Params.LinkTimeout << endl;
		}
	}

	void GetConPhy(CwclGattServerClient* const Client)
	{
		wclBluetoothLeConnectionPhy Phy;
		int Res = Client->GetConnectionPhyInfo(Phy);
		if (Res != WCL_E_SUCCESS)
			cout << "Get connection PHY failed: 0x" << hex << Res << dec << endl;
		else
		{
			cout << "Connection PHY [" << hex << Client->Address << dec << "]" << endl;
			cout << "  Receive" << endl;
			cout << "    IsCoded        : " << Phy.Receive.IsCoded << endl;
			cout << "    IsUncoded1MPhy : " << Phy.Receive.IsUncoded1MPhy << endl;
			cout << "    IsUncoded2MPhy : " << Phy.Receive.IsUncoded2MPhy << endl;
			cout << "  Transmit" << endl;
			cout << "    IsCoded        : " << Phy.Transmit.IsCoded << endl;
			cout << "    IsUncoded1MPhy : " << Phy.Transmit.IsUncoded1MPhy << endl;
			cout << "    IsUncoded2MPhy : " << Phy.Transmit.IsUncoded2MPhy << endl;
		}
	}

public:
	CGattServer()
	{
		FTerminateEvent = NULL;

		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);
	}

	void Run()
	{
		CwclMessageBroadcaster::SetSyncMethod(skApc);

		system("cls");
		cout << "The demo shows how to use GATT Server in IoT applications." << endl;
		cout << "The demo uses APC syncrhonization." << endl;

		FTerminateEvent = CreateEvent(NULL, TRUE, FALSE, NULL);

		FGattThread = (HANDLE)_beginthreadex(NULL, 0, _ServerThread, this, 0, NULL);

		system("pause");

		SetEvent(FTerminateEvent);
		WaitForSingleObject(FGattThread, INFINITE);
		CloseHandle(FGattThread);

		CloseHandle(FTerminateEvent);
	}
};

int main(int argc, char **argv)
{
	UNREFERENCED_PARAMETER(argc);
	UNREFERENCED_PARAMETER(argv);

	CGattServer* GattServer = new CGattServer();
	GattServer->Run();
	delete GattServer;
}