//---------------------------------------------------------------------------

#include <vcl.h>
#pragma hdrstop

#include <iostream>

#include "wclMessaging.hpp"
#include "wclErrors.hpp"
#include "wclBluetooth.hpp"

#pragma argsused
#pragma warn -8027
#pragma warn -8026

using namespace std;

class TGattClientApplication : TObject
{
private:
        TwclGattClient* FClient;
        TwclBluetoothManager* FManager;
        TwclBluetoothRadio* FRadio;

        HANDLE FDisconnectEvent;
        HANDLE FOperationEvent;

        unsigned long FIndex;
        TwclBluetoothAddresses FDevices;

        /* Bluetooth Manager event handlers. */

        void __fastcall BluetoothManagerAfterOpen(TObject* Sender)
        {
                cout << "Bluetooth Manager opened" << endl;
        }

        void __fastcall BluetoothManagerBeforeClose(TObject* Sender)
        {
                cout << "Bluetooth Manager is closing" << endl;
        }

        void __fastcall BluetoothManagerClosed(TObject* Sender)
		{
                cout << "Bluetooth Manager closed" << endl;
        }

		#ifndef _DXE3_
		void __fastcall BluetoothManagerDiscoveringCompleted(TObject* Sender,
				const TwclBluetoothRadio* Radio, const int Error)
		#else
		void __fastcall BluetoothManagerDiscoveringCompleted(TObject* Sender,
				TwclBluetoothRadio* const Radio, const int Error)
		#endif
		{
				cout << "Discovering completed with result: 0x" << hex << Error << dec << endl;
				SetEvent(FOperationEvent);
		}

		#ifndef _DXE3_
		void __fastcall BluetoothManagerDeviceFound(TObject* Sender,
				const TwclBluetoothRadio* Radio, const __int64 Address)
		#else
		void __fastcall BluetoothManagerDeviceFound(TObject* Sender,
				TwclBluetoothRadio* const Radio, const __int64 Address)
		#endif
		{
				String Name;
				int Res = const_cast<TwclBluetoothRadio*>(Radio)->GetRemoteName(
						Address, Name);
				if (Res != WCL_E_SUCCESS)
						Name = "<UNKNOWN>";

				FIndex++;
				cout << "[" << FIndex << "]: " << hex << Address << dec << " (" << Name.c_str() << ")" << endl;

				int Len = FDevices.Length;
				FDevices.Length = Len + 1;
				FDevices[Len] = Address;
		}

		#ifndef _DXE3_
		void __fastcall BluetoothManagerDiscoveringStarted(TObject* Sender,
				const TwclBluetoothRadio* Radio)
		#else
		void __fastcall BluetoothManagerDiscoveringStarted(TObject* Sender,
				TwclBluetoothRadio* const Radio)
        #endif
        {
                FIndex = 0;
                ResetEvent(FOperationEvent);

                cout << "Discovering started" << endl;
                FDevices.Length = 0;
        }

        /* Device helper methods. **/

        void __fastcall DumpValue(const TwclGattCharacteristicValue& Value)
        {
                if (Value.Length > 0)
                {
                        String s = "";
                        for (int i = 0; i < Value.Length; i++)
                                s = s + IntToHex(Value[i], 2);
                        cout << "Value: " << s.c_str() << endl;
                }
        }

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

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

        void __fastcall GetConnectionPhy()
        {
                TwclBluetoothLeConnectionPhy Phy;
                int Res = FClient->GetConnectionPhyInfo(Phy);
                if (Res != WCL_E_SUCCESS)
                        cout << "Get connection PHY error: 0x" << hex << Res << dec << endl;
                else
                {
                        cout << "Connection PHY: " << 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;
                }
        }

        String __fastcall UuidToString(const TwclGattUuid& Uuid)
        {
                if (Uuid.IsShortUuid)
                        return IntToHex(Uuid.ShortUuid, 4);
                return Sysutils::GUIDToString(Uuid.LongUuid);
        }

        /* GATT client event handlers. */

        void __fastcall ClientCharacteristicChanged(TObject* Sender,
                const unsigned short Handle,
                const TwclGattCharacteristicValue Value)
        {
                cout << "Characteristic [" << hex << Handle << "] value changed" << endl;
                DumpValue(Value);
        }

        void __fastcall ClientDisconnect(TObject* Sender, const int Reason)
        {
                cout << "Client disconnected with reason: 0x" << hex << Reason << dec << endl;
                SetEvent(FDisconnectEvent);
        }

        void __fastcall ClientConnect(TObject* Sender, const int Error)
        {
                if (Error != WCL_E_SUCCESS)
                        cout << "Connection failed: 0x" << hex << Error << dec << endl;
                else
                {
                        cout << "Client connected" << endl;
                        GetMaxPduSize();
                        GetConnectionParams();
                        GetConnectionPhy();
                }

                SetEvent(FOperationEvent);
        }

        void __fastcall ClientMaxPduSizeChanged(TObject* Sender)
        {
                cout << "Max PDU size changed" << endl;
                GetMaxPduSize();
        }

        void __fastcall ClientConnectionParamsChanged(TObject* Sender)
        {
                cout << "Connection parameters changed" << endl;
                GetConnectionParams();
        }

        void __fastcall ClientConnectionPhyChanged(TObject* Sender)
        {
                cout << "Connection PHY changed" << endl;
                GetConnectionPhy();
        }

        /* Bluetooth Framework initialization & finalization. */

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

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

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

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

                FRadio = NULL;
        }

        /* Communication functions. */

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

        void __fastcall DumpCharacteristicValue(
                const TwclGattCharacteristic& Characteristic)
        {
                TwclGattCharacteristicValue Value;
                if (Characteristic.IsReadable)
                {
                        cout << "    Read characteristic value" << endl;
                        int Res = FClient->ReadCharacteristicValue(
                                Characteristic, goNone, Value);
                        if (Res != WCL_E_SUCCESS)
                                cout << "    Read value failed: 0x" << hex << Res << dec << endl;
                        else
                                DumpValue(Value);
                }
        }

        void __fastcall SubscribeToCharacteristic(
                TwclGattCharacteristic Characteristic)
        {
                if (Characteristic.IsIndicatable || Characteristic.IsNotifiable)
                {
                        cout << "    Try to subscribe to characteristic" << endl;
                        if (Characteristic.IsIndicatable && Characteristic.IsNotifiable)
                                Characteristic.IsIndicatable = false;

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

        void __fastcall ReadCharacteristics(const TwclGattService& Service)
        {
                cout << "  Read characteristics" << endl;

                TwclGattCharacteristics Characteristics;
                int Res = FClient->ReadCharacteristics(Service, goNone,
                        Characteristics);
                if (Res != WCL_E_SUCCESS)
                        cout << "  Read characteristics failed: 0x" << hex << Res << dec << endl;
                else
                {
                        if (Characteristics.Length == 0)
                                cout << "  No characteristics found" << endl;
                        else
                        {
                                for (int i = 0; i < Characteristics.Length; i++)
                                {
                                        cout << "  Characteristic [" << i << "] ";
                                        cout << hex << Characteristics[i].Handle << dec;
                                        cout << " " << UuidToString(Characteristics[i].Uuid).c_str() << endl;
                                        DumpCharacteristicProperties(Characteristics[i]);
                                        DumpCharacteristicValue(Characteristics[i]);
                                        SubscribeToCharacteristic(Characteristics[i]);
                                }
                        }
                }
        }

        void __fastcall ReadServices()
        {
                cout << "Read services" << endl;

                TwclGattServices Services;
                int Res = FClient->ReadServices(goNone, Services);
                if (Res != WCL_E_SUCCESS)
                        cout << "Read services failed: 0x" << hex << Res << dec << endl;
                else
                {
                        if (Services.Length == 0)
                                cout << "No services found" << endl;
                        else
                        {
                                for (int i = 0; i < Services.Length; i++)
                                {
                                        cout << "Service [" << i + 1 << "] ";
                                        cout << " (" << hex << Services[i].Handle << dec << ") ";
                                        cout << UuidToString(Services[i].Uuid).c_str() << endl;

                                        ReadCharacteristics(Services[i]);
                                }
                        }
                }
        }

        /* Application functions. */

        void __fastcall ShowFoundDevices()
        {
                if (FDevices.Length > 0)
                {
                        for (int i = 0; i < FDevices.Length; i++)
                        {
                                String Name;
                                int Res = FRadio->GetRemoteName(FDevices[i], Name);
                                if (Res != WCL_E_SUCCESS)
                                        Name = "<UNKNOWN>";

                                cout << "[" << i + 1 << "]: " << hex << FDevices[i] << dec;
                                cout << " (" << Name.c_str() << ")" << endl;
                        }
                }
        }

        bool __fastcall ConnectToDevice(const __int64 Address)
        {
                cout << "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)
                {
                        cout << "Start connection failed: 0x" << hex << Res << dec << endl;
                        system("pause");
                        return false;
                }

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

                        system("pause");

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

                        return true;
                }

                system("pause");
                return false;
        }

        void __fastcall SearchDevices()
        {
                cout << "Start searching for Bluetooth LE devices" << endl;
                int Res = FRadio->Discover(10, dkBle);
                if (Res != WCL_E_SUCCESS)
                {
                        cout << "Start searching devices failed: 0x" << hex << Res << dec << endl;
                        system("pause");
                }
                else
                {
                        WaitForSingleObject(FOperationEvent, INFINITE);

                        if (FDevices.Length == 0)
                        {
                                cout << endl;
                                cout << "No Bluetooth LE devices found" << endl;
                                system("pause");
                        }
                        else
                        {
                                while (true)
                                {
                                        cout << endl;
                                        cout << "Enter device index or 0 to exit: " << endl;

                                        int Index;
                                        cin >> Index;

                                        if (Index == 0)
                                                return;

                                        Index--;

                                        if (Index >= FDevices.Length)
                                        {
                                                cout << "Invalid device index" << endl;
                                                continue;
                                        }

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

                                        ShowFoundDevices();
                                }
                        }
                }
        }

        void __fastcall RunApplicationLoop()
        {
                bool Terminate = false;
                while (!Terminate)
                {
                        cout << endl << endl;
                        cout << "Select option:" << endl;
                        cout << "    1 - Search for GATT enabled devices" << endl;
                        cout << "    0 - Exit" << endl;

                        int Opt;
                        cin >> Opt;
                        switch (Opt)
                        {
                                case 0:
                                        Terminate = true;
                                        break;
                                case 1:
                                        SearchDevices();
                                        break;
                                default:
                                        cout << "Invalid option selected. Try again" << endl;
                                        break;
                        }
                }
        }

public:
        /* Constructor & destructor. */

        __fastcall TGattClientApplication()
                : TObject()
        {
                FClient = new TwclGattClient(NULL);
                FClient->OnCharacteristicChanged = ClientCharacteristicChanged;
                FClient->OnConnect = ClientConnect;
                FClient->OnConnectionParamsChanged = ClientConnectionParamsChanged;
                FClient->OnConnectionPhyChanged = ClientConnectionPhyChanged;
                FClient->OnDisconnect = ClientDisconnect;
                FClient->OnMaxPduSizeChanged = ClientMaxPduSizeChanged;

                FManager = new TwclBluetoothManager(NULL);
                FManager->MessageProcessing = mpAsync;
                FManager->AfterOpen = BluetoothManagerAfterOpen;
                FManager->BeforeClose = BluetoothManagerBeforeClose;
                FManager->OnClosed = BluetoothManagerClosed;
                FManager->OnDiscoveringCompleted = BluetoothManagerDiscoveringCompleted;
                FManager->OnDeviceFound = BluetoothManagerDeviceFound;
                FManager->OnDiscoveringStarted = BluetoothManagerDiscoveringStarted;

                FRadio = NULL;

                FDevices.Length = 0;

                FDisconnectEvent = NULL;
                FOperationEvent = NULL;
        }

        virtual __fastcall ~TGattClientApplication()
        {
                FClient->Free();
                FManager->Free();
        }

        /* Control methods. */

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

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

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

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

                        FinalizeBluetoothFramework();
                }
        }
};

//---------------------------------------------------------------------------

int main(int argc, char* argv[])
{
		TGattClientApplication* Application = new TGattClientApplication();
		Application->Run();
		delete Application;

        system("pause");
        
        return 0;
}
//---------------------------------------------------------------------------

