text: //
  //  callbacks.cc
  //  noble-mac-native
  //
  //  Created by Georg Vienna on 30.08.18.
  //
  #include "callbacks.h"
  
  #include <napi-thread-safe-callback.hpp>
  
  #define _s(val) Napi::String::New(env, val)
  #define _b(val) Napi::Boolean::New(env, val)
  #define _n(val) Napi::Number::New(env, val)
  #define _u(str) toUuid(env, str)
  
  Napi::String toUuid(Napi::Env& env, const std::string& uuid) {
      std::string str(uuid);
      str.erase(std::remove(str.begin(), str.end(), '-'), str.end());
      std::transform(str.begin(), str.end(), str.begin(), ::tolower);
      return _s(str);
  }
  
  Napi::String toAddressType(Napi::Env& env, const AddressType& type) {
      if(type == PUBLIC) {
          return _s("public");
      } else if (type == RANDOM) {
          return _s("random");
      }
      return _s("unknown");
  }
  
  Napi::Buffer<uint8_t> toBuffer(Napi::Env& env, const Data& data) {
      if (data.empty()) {
          return Napi::Buffer<uint8_t>::New(env, 0);
      }
      return Napi::Buffer<uint8_t>::Copy(env, &data[0], data.size());
  }
  
  Napi::Array toUuidArray(Napi::Env& env, const std::vector<std::string>& data) {
      if (data.empty()) {
          return Napi::Array::New(env);
      }
      auto arr = Napi::Array::New(env, data.size());
      for (size_t i = 0; i < data.size(); i++) {
          arr.Set(i, _u(data[i]));
      }
      return arr;
  }
  
  Napi::Array toArray(Napi::Env& env, const std::vector<std::string>& data) {
      if (data.empty()) {
          return Napi::Array::New(env);
      }
      auto arr = Napi::Array::New(env, data.size());
      for (size_t i = 0; i < data.size(); i++) {
          arr.Set(i, _s(data[i]));
      }
      return arr;
  }
  
  void Emit::Wrap(const Napi::Value& receiver, const Napi::Function& callback) {
      mCallback = std::make_shared<ThreadSafeCallback>(receiver, callback);
  }
  
  void Emit::RadioState(const std::string& state) {
      mCallback->call([state](Napi::Env env, std::vector<napi_value>& args) {
          // emit('stateChange', state);
          args = { _s("stateChange"), _s(state) };
      });
  }
  
  void Emit::ScanState(bool start) {
      mCallback->call([start](Napi::Env env, std::vector<napi_value>& args) {
          // emit('scanStart') emit('scanStop')
          args = { _s(start ? "scanStart" : "scanStop") };
      });
  }
  
  void Emit::Scan(const std::string& uuid, int rssi, const Peripheral& peripheral) {
      auto address = peripheral.address;
      auto addressType = peripheral.addressType;
      auto connectable = peripheral.connectable;
      auto name = peripheral.name;
      auto txPowerLevel = peripheral.txPowerLevel;
      auto manufacturerData = peripheral.manufacturerData;
      auto serviceData = peripheral.serviceData;
      auto serviceUuids = peripheral.serviceUuids;
      mCallback->call([uuid, rssi, address, addressType, connectable, name, txPowerLevel, manufacturerData, serviceData, serviceUuids](Napi::Env env, std::vector<napi_value>& args) {
          Napi::Object advertisment = Napi::Object::New(env);
          advertisment.Set(_s("localName"), _s(name));
          advertisment.Set(_s("txPowerLevel"), txPowerLevel);
          advertisment.Set(_s("manufacturerData"), toBuffer(env, manufacturerData));
          auto array = serviceData.empty() ? Napi::Array::New(env) : Napi::Array::New(env, serviceData.size());
          for (size_t i = 0; i < serviceData.size(); i++) {
              Napi::Object data = Napi::Object::New(env);
              data.Set(_s("uuid"), _u(serviceData[i].first));
              data.Set(_s("data"), toBuffer(env, serviceData[i].second));
              array.Set(i, data);
          }
          advertisment.Set(_s("serviceData"), array);
          advertisment.Set(_s("serviceUuids"), toUuidArray(env, serviceUuids));
          // emit('discover', deviceUuid, address, addressType, connectable, advertisement, rssi);
          args = { _s("discover"), _u(uuid), _s(address), toAddressType(env, addressType), _b(connectable), advertisment, _n(rssi) };
      });
  }
  
  void Emit::Connected(const std::string& uuid, const std::string& error) {
      mCallback->call([uuid, error](Napi::Env env, std::vector<napi_value>& args) {
          // emit('connect', deviceUuid) error added here
          args = { _s("connect"), _u(uuid), error.empty() ? env.Null() : _s(error) };
      });
  }
  
  void Emit::Disconnected(const std::string& uuid) {
      mCallback->call([uuid](Napi::Env env, std::vector<napi_value>& args) {
          // emit('disconnect', deviceUuid);
          args = { _s("disconnect"), _u(uuid) };
      });
  }
  
  void Emit::RSSI(const std::string & uuid, int rssi) {
      mCallback->call([uuid, rssi](Napi::Env env, std::vector<napi_value>& args) {
          // emit('rssiUpdate', deviceUuid, rssi);
          args = { _s("rssiUpdate"), _u(uuid), _n(rssi) };
      });
  }
  
  void Emit::ServicesDiscovered(const std::string & uuid, const std::vector<std::string>& serviceUuids) {
      mCallback->call([uuid, serviceUuids](Napi::Env env, std::vector<napi_value>& args) {
          // emit('servicesDiscover', deviceUuid, serviceUuids)
          args = { _s("servicesDiscover"), _u(uuid), toUuidArray(env, serviceUuids) };
      });
  }
  
  void Emit::IncludedServicesDiscovered(const std::string & uuid, const std::string & serviceUuid, const std::vector<std::string>& serviceUuids) {
      mCallback->call([uuid, serviceUuid, serviceUuids](Napi::Env env, std::vector<napi_value>& args) {
          // emit('includedServicesDiscover', deviceUuid, serviceUuid, includedServiceUuids)
          args = { _s("includedServicesDiscover"), _u(uuid), _u(serviceUuid), toUuidArray(env, serviceUuids) };
      });
  }
  
  void Emit::CharacteristicsDiscovered(const std::string & uuid, const std::string & serviceUuid, const std::vector<std::pair<std::string, std::vector<std::string>>>& characteristics) {
      mCallback->call([uuid, serviceUuid, characteristics](Napi::Env env, std::vector<napi_value>& args) {
          auto arr = characteristics.empty() ? Napi::Array::New(env) : Napi::Array::New(env, characteristics.size());
          for (size_t i = 0; i < characteristics.size(); i++) {
              Napi::Object characteristic = Napi::Object::New(env);
              characteristic.Set(_s("uuid"), _u(characteristics[i].first));
              characteristic.Set(_s("properties"), toArray(env, characteristics[i].second));
              arr.Set(i, characteristic);
          }
          // emit('characteristicsDiscover', deviceUuid, serviceUuid, { uuid, properties: ['broadcast', 'read', ...]})
          args = { _s("characteristicsDiscover"), _u(uuid), _u(serviceUuid), arr };
      });
  }
  
  void Emit::Read(const std::string & uuid, const std::string & serviceUuid, const std::string & characteristicUuid, const Data& data, bool isNotification) {
      mCallback->call([uuid, serviceUuid, characteristicUuid, data, isNotification](Napi::Env env, std::vector<napi_value>& args) {
          // emit('read', deviceUuid, serviceUuid, characteristicsUuid, data, isNotification);
          args = { _s("read"), _u(uuid), _u(serviceUuid), _u(characteristicUuid), toBuffer(env, data), _b(isNotification) };
      });
  }
  
  void Emit::Write(const std::string & uuid, const std::string & serviceUuid, const std::string & characteristicUuid) {
      mCallback->call([uuid, serviceUuid, characteristicUuid](Napi::Env env, std::vector<napi_value>& args) {
          // emit('write', deviceUuid, servicesUuid, characteristicsUuid)
          args = { _s("write"), _u(uuid), _u(serviceUuid), _u(characteristicUuid) };
      });
  }
  
  void Emit::Notify(const std::string & uuid, const std::string & serviceUuid, const std::string & characteristicUuid, bool state) {
      mCallback->call([uuid, serviceUuid, characteristicUuid, state](Napi::Env env, std::vector<napi_value>& args) {
          // emit('notify', deviceUuid, servicesUuid, characteristicsUuid, state)
          args = { _s("notify"), _u(uuid), _u(serviceUuid), _u(characteristicUuid), _b(state) };
      });
  }
  
  void Emit::DescriptorsDiscovered(const std::string & uuid, const std::string & serviceUuid, const std::string & characteristicUuid, const std::vector<std::string>& descriptorUuids) {
      mCallback->call([uuid, serviceUuid, characteristicUuid, descriptorUuids](Napi::Env env, std::vector<napi_value>& args) {
          // emit('descriptorsDiscover', deviceUuid, servicesUuid, characteristicsUuid, descriptors: [uuids])
          args = { _s("descriptorsDiscover"), _u(uuid), _u(serviceUuid), _u(characteristicUuid), toUuidArray(env, descriptorUuids) };
      });
  }
  
  void Emit::ReadValue(const std::string & uuid, const std::string & serviceUuid, const std::string & characteristicUuid, const std::string& descriptorUuid, const Data& data) {
      mCallback->call([uuid, serviceUuid, characteristicUuid, descriptorUuid, data](Napi::Env env, std::vector<napi_value>& args) {
          // emit('valueRead', deviceUuid, serviceUuid, characteristicUuid, descriptorUuid, data)
          args = { _s("valueRead"), _u(uuid), _u(serviceUuid), _u(characteristicUuid), _u(descriptorUuid), toBuffer(env, data) };
      });
  }
  
  void Emit::WriteValue(const std::string & uuid, const std::string & serviceUuid, const std::string & characteristicUuid, const std::string& descriptorUuid) {
      mCallback->call([uuid, serviceUuid, characteristicUuid, descriptorUuid](Napi::Env env, std::vector<napi_value>& args) {
          // emit('valueWrite', deviceUuid, serviceUuid, characteristicUuid, descriptorUuid);
          args = { _s("valueWrite"), _u(uuid), _u(serviceUuid), _u(characteristicUuid), _u(descriptorUuid) };
      });
  }
  
  void Emit::ReadHandle(const std::string & uuid, int descriptorHandle, const Data& data) {
      mCallback->call([uuid, descriptorHandle, data](Napi::Env env, std::vector<napi_value>& args) {
          // emit('handleRead', deviceUuid, descriptorHandle, data);
          args = { _s("handleRead"), _u(uuid), _n(descriptorHandle), toBuffer(env, data) };
      });
  }
  
  void Emit::WriteHandle(const std::string & uuid, int descriptorHandle) {
      mCallback->call([uuid, descriptorHandle](Napi::Env env, std::vector<napi_value>& args) {
          // emit('handleWrite', deviceUuid, descriptorHandle);
          args = { _s("handleWrite"), _u(uuid), _n(descriptorHandle) };
      });
  }
  