// 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;

class CGattClientApplication
{
private:
	typedef vector<__int64> DEVICES;

	CwclGattClient* FClient; 
	CwclBluetoothManager* FManager;
	CwclBluetoothRadio* FRadio;

	HANDLE FDisconnectEvent;
	HANDLE FOperationEvent;

	unsigned int FIndex;
	DEVICES FDevices;

	/* Bluetooth Manager event handlers. */

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

		wcout << _T("Bluetooth Manager opened") << endl;
	}

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

		wcout << _T("Bluetooth Manager is closing") << endl;
	}

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

		wcout << _T("Bluetooth Manager closed") << endl;
	}

	void BluetoothManagerDiscoveringCompleted(void* Sender, CwclBluetoothRadio* const Radio,
		const int Error)
	{
		UNREFERENCED_PARAMETER(Sender);
		UNREFERENCED_PARAMETER(Radio);

		wcout << _T("Discovering completed with result: 0x") << hex << Error << dec << endl;
		SetEvent(FOperationEvent);
	}

	void BluetoothManagerDeviceFound(void* Sender, CwclBluetoothRadio* const Radio,
		const __int64 Address)
	{
		UNREFERENCED_PARAMETER(Sender);
		UNREFERENCED_PARAMETER(Radio);

		tstring Name;
		int Res = Radio->GetRemoteName(Address, Name);
		if (Res != WCL_E_SUCCESS)
			Name = _T("<UNKNOWN>");

		FIndex++;
		wcout << _T("[") << FIndex << _T("]: ") << hex << Address << dec;
		wcout << _T(" (") << Name.c_str() << _T(")") << endl;
		FDevices.push_back(Address);
	}

	void BluetoothManagerDiscoveringStarted(void* Sender, CwclBluetoothRadio* const Radio)
	{
		UNREFERENCED_PARAMETER(Sender);
		UNREFERENCED_PARAMETER(Radio);

		FIndex = 0;
		ResetEvent(FOperationEvent);

		wcout << _T("Discovering started") << endl;
		FDevices.clear();
	}

	/* Device helper methods. */

	void DumpValue(const unsigned char* const Value, const unsigned long Length) const
	{
		if (Value != NULL && Length > 0)
		{
			string s = "";
			for (unsigned long i = 0; i < Length; i++)
			{
				char c[2];
				snprintf(c, 2, "%.2x", Value[i]);
				s += string(c);
			}
			cout << "Value: " << s.c_str() << endl;
		}
	}

	void GetMaxPduSize()
	{
		unsigned short Size;
		int Res = FClient->GetMaxPduSize(Size);
		if (Res != WCL_E_SUCCESS)
			wcout << _T("Get max PDU size error: 0x") << hex << Res << dec << endl;
		else
			wcout << _T("Max PDU size: ") << Size << endl;
	}

	void GetConnectionParams()
	{
		wclBluetoothLeConnectionParameters Params;
		int Res = FClient->GetConnectionParams(Params);
		if (Res != WCL_E_SUCCESS)
			wcout << _T("Get connection params error: 0x") << hex << Res << dec << endl;
		else
		{
			wcout << _T("Connection params: ") << endl;
			wcout << _T("  Interval     : ") << Params.Interval << endl;
			wcout << _T("  Latency      : ") << Params.Latency << endl;
			wcout << _T("  Link Timeout : ") << Params.LinkTimeout << endl;
		}
	}

	void GetConnectionPhy()
	{
		wclBluetoothLeConnectionPhy Phy;
		int Res = FClient->GetConnectionPhyInfo(Phy);
		if (Res != WCL_E_SUCCESS)
			wcout << _T("Get connection PHY error: 0x") << hex << Res << dec << endl;
		else
		{
			wcout << _T("Connection PHY: ") << endl;
			wcout << _T("  Receive") << endl;
			wcout << _T("    IsCoded        : ") << Phy.Receive.IsCoded << endl;
			wcout << _T("    IsUncoded1MPhy : ") << Phy.Receive.IsUncoded1MPhy << endl;
			wcout << _T("    IsUncoded2MPhy : ") << Phy.Receive.IsUncoded2MPhy << endl;
			wcout << _T("  Transmit") << endl;
			wcout << _T("    IsCoded        : ") << Phy.Transmit.IsCoded << endl;
			wcout << _T("    IsUncoded1MPhy : ") << Phy.Transmit.IsUncoded1MPhy << endl;
			wcout << _T("    IsUncoded2MPhy : ") << Phy.Transmit.IsUncoded2MPhy << endl;
		}
	}

	tstring UuidToString(const wclGattUuid& Uuid) const
	{
		WCHAR str[255] = { 0 };

		if (Uuid.IsShortUuid)
		{
			wsprintf(str, _T("%.4x"), Uuid.ShortUuid);
			return tstring(str);
		}

		GUID guid = Uuid.LongUuid;
		wsprintf(str, _T("{%.8x-%.4x-%.4x-%.2x%.2x-%.2x%.2x%.2x%.2x%.2x%.2x}"),
			guid.Data1, guid.Data2, guid.Data3,
			guid.Data4[0], guid.Data4[1], guid.Data4[2], guid.Data4[3],
			guid.Data4[4], guid.Data4[5], guid.Data4[6], guid.Data4[7]);

		return tstring(str);
	}

	/* GATT client event handlers. */

	void ClientCharacteristicChanged(void* Sender, const unsigned short Handle,
		const unsigned char* const Value, const unsigned long Length)
	{
		UNREFERENCED_PARAMETER(Sender);
		UNREFERENCED_PARAMETER(Value);
		UNREFERENCED_PARAMETER(Length);

		wcout << _T("Characteristic [") << hex << Handle << dec << _T("] value changed") << endl;
		DumpValue(Value, Length);
	}

	void ClientDisconnect(void* Sender, const int Reason)
	{
		UNREFERENCED_PARAMETER(Sender);

		wcout << _T("Client disconnected with reason: 0x") << hex << Reason << dec << endl;
		SetEvent(FDisconnectEvent);
	}

	void ClientConnect(void* Sender, const int Error)
	{
		UNREFERENCED_PARAMETER(Sender);

		if (Error != WCL_E_SUCCESS)
			wcout << _T("Connection failed: 0x") << hex << Error << dec << endl;
		else
		{
			wcout << _T("Client connected") << endl;
			GetMaxPduSize();
			GetConnectionParams();
			GetConnectionPhy();
		}

		SetEvent(FOperationEvent);
	}

	void ClientMaxPduSizeChanged(void* Sender)
	{
		UNREFERENCED_PARAMETER(Sender);

		wcout << _T("Max PDU size changed") << endl;
		GetMaxPduSize();
	}

	void ClientConnectionParamsChanged(void* Sender)
	{
		UNREFERENCED_PARAMETER(Sender);

		wcout << _T("Connection parameters changed") << endl;
		GetConnectionParams();
	}

	void ClientConnectionPhyChanged(void* Sender)
	{
		UNREFERENCED_PARAMETER(Sender);

		wcout << _T("Connection PHY changed") << endl;
		GetConnectionPhy();
	}

	/* Bluetooth Framework initialization & finalization. */

	bool InitializeBluetoothFramework()
	{
		wcout << _T("Opening Bluetooth Manager") << endl;
		int Res = FManager->Open();
		if (Res != WCL_E_SUCCESS)
		{
			wcout << _T("Initialize Bluetooth Manager failed: 0x") << hex << Res << dec << endl;
			return false;
		}

		wcout << _T("Try to get working Bluetooth LE radio") << endl;
		Res = FManager->GetLeRadio(FRadio);
		if (Res != WCL_E_SUCCESS)
		{
			wcout << _T("Get working Bluetooth LE radio failed: 0x") << hex << Res << dec << endl;
			FManager->Close();
			return false;
		}

		wcout << _T("Use ") << FRadio->ApiName.c_str() << " Bluetooth radio" << endl;
		return true;
	}

	void FinalizeBluetoothFramework()
	{
		wcout << _T("Closing Bluetooth Manager") << endl;
		int Res = FManager->Close();
		if (Res != WCL_E_SUCCESS)
			wcout << _T("Close Bluetooth Manager failed: 0x") << hex << Res << dec << endl;
		
		FRadio = nullptr;
	}

	/* Communication functions. */

	void DumpCharacteristicProperties(const wclGattCharacteristic& Characteristic)
	{
		wcout << _T("    Properties: ");
		if (Characteristic.IsBroadcastable)
			wcout << _T("Broadcastable ");
		if (Characteristic.IsReadable)
			wcout << _T("Readable ");
		if (Characteristic.IsWritable)
			wcout << _T("Writable ");
		if (Characteristic.IsWritableWithoutResponse)
			wcout << _T("WritableWithoutResponse ");
		if (Characteristic.IsSignedWritable)
			wcout << _T("SignedWritable ");
		if (Characteristic.IsNotifiable)
			wcout << _T("Notifiable ");
		if (Characteristic.IsIndicatable)
			wcout << _T("Indicatable ");
		wcout << endl;
	}

	void DumpCharacteristicValue(const wclGattCharacteristic& Characteristic)
	{
		if (Characteristic.IsReadable)
		{
			wcout << _T("    Read characteristic value") << endl;
			unsigned char* Value = nullptr;
			unsigned long Length = 0;
			int Res = FClient->ReadCharacteristicValue(Characteristic, goNone, Value, Length);
			if (Res != WCL_E_SUCCESS)
				wcout << _T("    Read value failed: 0x") << hex << Res << dec << endl;
			else
				DumpValue(Value, Length);
		}
	}

	void SubscribeToCharacteristic(wclGattCharacteristic& Characteristic)
	{
		if (Characteristic.IsIndicatable || Characteristic.IsNotifiable)
		{
			wcout << _T("    Try to subscribe to characteristic") << endl;
			if (Characteristic.IsIndicatable && Characteristic.IsNotifiable)
				Characteristic.IsIndicatable = false;

			int Res = FClient->SubscribeForNotifications(Characteristic);
			if (Res != WCL_E_SUCCESS)
				wcout << _T("    Subscribe failed: 0x") << hex << Res << dec << endl;
			else
				wcout << _T("    Subscribed") << endl;
		}
	}

	void ReadCharacteristics(const wclGattService& Service)
	{
		wcout << _T("  Read characteristics") << endl;
		wclGattCharacteristics Characteristics;
		int Res = FClient->ReadCharacteristics(Service, goNone, Characteristics);
		if (Res != WCL_E_SUCCESS)
		{
			wcout << _T("  Read characteristics failed: 0x") << hex << Res << dec << endl;
			return;
		}

		if (Characteristics.size() == 0)
		{
			wcout << _T("  No characteristics found") << endl;
			return;
		}

		int Ndx = 0;
		for (wclGattCharacteristics::iterator Characteristic = Characteristics.begin(); Characteristic != Characteristics.end(); Characteristic++)
		{
			wcout << _T("  Characteristic [") << Ndx << _T("] ") << hex << Characteristic->Handle << dec;
			wcout << _T(" ") << UuidToString(Characteristic->Uuid) << endl;
			DumpCharacteristicProperties(*Characteristic);
			DumpCharacteristicValue(*Characteristic);
			SubscribeToCharacteristic(*Characteristic);
		}
	}

	void ReadServices()
	{
		wcout << _T("Read services") << endl;
		wclGattServices Services;
		int Res = FClient->ReadServices(goNone, Services);
		if (Res != WCL_E_SUCCESS)
		{
			wcout << _T("Read services failed: 0x") << hex << Res << dec << endl;
			return;
		}

		if (Services.size() == 0)
		{
			wcout << _T("No services found") << endl;
			return;
		}

		int Ndx = 1;
		for (wclGattServices::iterator Service = Services.begin(); Service != Services.end(); Service++)
		{
			wcout << _T("Service [") << Ndx << _T("] ");
			wcout << _T(" (") << hex << Service->Handle << _T(") ") << dec;
			wcout << UuidToString(Service->Uuid) << endl;

			ReadCharacteristics(*Service);
			Ndx++;
		}
	}

	/* Application functions. */

	void ShowFoundDevices()
	{
		system("cls");

		unsigned long Index = 1;
		for (DEVICES::iterator Address = FDevices.begin(); Address != FDevices.end(); Address++)
		{
			tstring Name;
			int Res = FRadio->GetRemoteName(*Address, Name);
			if (Res != WCL_E_SUCCESS)
				Name = _T("<UNKNOWN>");

			wcout << _T("[") << Index << _T("]: ") << hex << *Address << dec;
			wcout << _T(" (") << Name.c_str() << _T(")") << endl;
			Index++;
		}
	}

	bool ConnectToDevice(const __int64 Address)
	{
		system("cls");
		wcout << _T("Try to connect to device: ") << hex << Address << dec << endl;

		ResetEvent(FDisconnectEvent);
		ResetEvent(FOperationEvent);

		FClient->Address = Address;
		int Res = FClient->Connect(FRadio);
		if (Res != WCL_E_SUCCESS)
		{
			wcout << _T("Start connection failed: 0x") << hex << Res << dec << endl;
			system("pause");
			return false;
		}

		wcout << _T("Connection started") << endl;
		WaitForSingleObject(FOperationEvent, INFINITE);
		if (FClient->State == csConnected)
		{
			ReadServices();

			system("pause");

			FClient->Disconnect();
			WaitForSingleObject(FDisconnectEvent, INFINITE);
			wcout << _T("Client disconnected") << endl;

			return true;
		}
		
		system("pause");
		return false;
	}

	void SearchDevices()
	{
		system("cls");
		wcout << _T("Start searching for Bluetooth LE devices") << endl;
		int Res = FRadio->Discover(10, dkBle);
		if (Res != WCL_E_SUCCESS)
		{
			wcout << _T("Start searching devices failed: 0x") << hex << Res << dec << endl;
			system("pause");
			return;
		}

		WaitForSingleObject(FOperationEvent, INFINITE);

		if (FDevices.size() == 0)
		{
			wcout << endl << _T("No Bluetooth LE devices found") << endl;
			system("pause");
			return;
		}
		
		while (true)
		{
			wcout << endl << _T("Enter device index or 0 to exit: ");
			size_t Index;
			cin >> Index;

			if (Index == 0)
				return;

			Index--;
			if (Index >= FDevices.size())
			{
				wcout << _T("Invalid device index") << endl;
				continue;
			}

			if (ConnectToDevice(FDevices[Index]))
				return;

			ShowFoundDevices();
		}
	}

	void RunApplicationLoop()
	{
		bool Terminate = false;
		while (!Terminate)
		{
			wcout << endl << endl;
			wcout << _T("Select option:") << endl;
			wcout << _T("    1 - Search for GATT enabled devices") << endl;
			wcout << _T("    0 - Exit") << endl;
			int Opt;
			cin >> Opt;

			switch (Opt)
			{
				case 0:
					Terminate = true;
					continue;

				case 1:
					SearchDevices();
					break;

				default:
					wcout << _T("Invalid option selected. Try again") << endl;
					continue;
			}

			system("cls");
		}
	}
	
public:
	/* Constructor & destructor. */

	CGattClientApplication()
	{
		FClient = new CwclGattClient();
		__hook(&CwclGattClient::OnCharacteristicChanged, FClient, &CGattClientApplication::ClientCharacteristicChanged);
		__hook(&CwclGattClient::OnConnect, FClient, &CGattClientApplication::ClientConnect);
		__hook(&CwclGattClient::OnConnectionParamsChanged, FClient, &CGattClientApplication::ClientConnectionParamsChanged);
		__hook(&CwclGattClient::OnConnectionPhyChanged, FClient, &CGattClientApplication::ClientConnectionPhyChanged);
		__hook(&CwclGattClient::OnDisconnect, FClient, &CGattClientApplication::ClientDisconnect);
		__hook(&CwclGattClient::OnMaxPduSizeChanged, FClient, &CGattClientApplication::ClientMaxPduSizeChanged);

		FManager = new CwclBluetoothManager();
		FManager->MessageProcessing = mpAsync;
		__hook(&CwclBluetoothManager::AfterOpen, FManager, &CGattClientApplication::BluetoothManagerAfterOpen);
		__hook(&CwclBluetoothManager::BeforeClose, FManager, &CGattClientApplication::BluetoothManagerBeforeClose);
		__hook(&CwclBluetoothManager::OnClosed, FManager, &CGattClientApplication::BluetoothManagerClosed);
		__hook(&CwclBluetoothManager::OnDiscoveringCompleted, FManager, &CGattClientApplication::BluetoothManagerDiscoveringCompleted); 
		__hook(&CwclBluetoothManager::OnDeviceFound, FManager, &CGattClientApplication::BluetoothManagerDeviceFound);
		__hook(&CwclBluetoothManager::OnDiscoveringStarted, FManager, &CGattClientApplication::BluetoothManagerDiscoveringStarted);
		

		FRadio = nullptr;

		FDevices.clear();

		FDisconnectEvent = NULL;
		FOperationEvent = NULL;
	}

	~CGattClientApplication()
	{
		__unhook(FClient);
		delete FClient;

		__unhook(FManager);
		delete FManager;

		FDevices.clear();
	}

	/* Control methods. */

	void Run()
	{
		system("cls");
		wcout << _T("This demo shows how to use wclGATTClient in console/service applications with ");
		wcout << _T("Asyncrhonous message processing.") << endl << endl;

		if (!InitializeBluetoothFramework())
			return;

		FOperationEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
		if (FOperationEvent != NULL)
		{
			FDisconnectEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
			if (FDisconnectEvent != NULL)
			{
				RunApplicationLoop();

				CloseHandle(FDisconnectEvent);
			}
			else
				wcout << _T("Create disconnect event failed") << endl;

			CloseHandle(FOperationEvent);
		}
		else
			wcout << _T("Create operation event failed") << endl;

		FinalizeBluetoothFramework();
	}
};

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

	CGattClientApplication Application;
	Application.Run();

	system("pause");
}