text: #include <string>
  #include <algorithm>
  #include <iterator>
  #include <thread>
  #include <vector>
  #include <event-source-base.h>
  #include "services.h"
  #include "../wstring.h"
  #include <Windows.h>
  using namespace std;
  using namespace Napi;
  using namespace EventSourceBase;
  
  struct Service_item {
      Service_item(wstring&& name, wstring&& display_name, int status) 
      : name(name), display_name(display_name), status(status) {}
      wstring name;
      wstring display_name;
      int status;
  };
  
  class Events : public EventSourceBase {
  public:
      Events(const Napi::Function& callback)
      : EventSourceBase(callback) { }
  
      void OnEvent() override;
      void start();
      void stop();
      void send_event() { SendEvent(); }
  private:
      bool is_running{false};
  };
  
  vector<Service_item> service_items;
  vector<Service_item> event_result;
  SC_HANDLE service_handle{0};
  vector<Events*> events;
  
  auto get_services(SC_HANDLE handle) {
      unsigned long byte_count{0};
      unsigned long number_of_services{0};
      unsigned long resume_handle{0};
      EnumServicesStatusExW(handle, SC_ENUM_PROCESS_INFO, SERVICE_WIN32, SERVICE_STATE_ALL, nullptr, 0, 
          &byte_count, &number_of_services, &resume_handle, nullptr);
      vector<unsigned char> bytes(byte_count);
      EnumServicesStatusExW(handle, SC_ENUM_PROCESS_INFO, SERVICE_WIN32, SERVICE_STATE_ALL, 
          reinterpret_cast<unsigned char*>(bytes.data()), byte_count, &byte_count, &number_of_services, &resume_handle, nullptr);
  
      auto services = vector<ENUM_SERVICE_STATUS_PROCESSW>(reinterpret_cast<ENUM_SERVICE_STATUS_PROCESSW*>(bytes.data()), 
           reinterpret_cast<ENUM_SERVICE_STATUS_PROCESSW*>(bytes.data()) + number_of_services);
  
      vector<Service_item> result;
      transform(services.cbegin(), services.cend(), back_inserter(result), [](ENUM_SERVICE_STATUS_PROCESSW item) { 
          return Service_item(item.lpServiceName, item.lpDisplayName, item.ServiceStatusProcess.dwCurrentState);
      });    
      return result;
  }
  
  auto GetServices(const CallbackInfo& info) -> Value {
      auto handle = service_handle ? service_handle : OpenSCManagerW(nullptr, nullptr, SC_MANAGER_ENUMERATE_SERVICE);
      service_items = get_services(handle);
  
      auto result = Array::New(info.Env(), service_items.size());
      int i{0};
      for (auto item: service_items) {
          auto obj = Object::New(info.Env());
  
          obj.Set("name", WString::New(info.Env(), item.name));
          obj.Set("displayName", WString::New(info.Env(), item.display_name));
          obj.Set("status", Number::New(info.Env(), item.status));
  
          result.Set(i++, obj);
      }
  
      if (!service_handle)
          CloseServiceHandle(handle);
      return result;
  }
  
  auto RegisterServiceEvents(const CallbackInfo& info) -> Value { 
      if (events.empty()) 
          service_handle = OpenSCManagerW(nullptr, nullptr, SC_MANAGER_ENUMERATE_SERVICE);
  
      auto event = new Events(info[0].As<Function>());
      events.push_back(event);
      event->start();
      if (events.size() == 1) {
          thread thread([] {
              while (true) {
                  Sleep(500);
                  if (events.empty())
                      break;
                  auto new_service_items = get_services(service_handle);
                  unordered_map<wstring, Service_item> old_services_map;
                  transform(service_items.begin(), service_items.end(), inserter(old_services_map, old_services_map.end()),
                      [](auto service_item) { return make_pair(service_item.name, service_item); });
  
                  auto changes = remove_if(new_service_items.begin(), new_service_items.end(), [old_services_map](auto service_item) {
                      auto it = old_services_map.find(service_item.name);
                      return it != old_services_map.end()
                      ? service_item.status == it->second.status
                      : true;
                  }); 
  
                  event_result.clear();
                  transform(new_service_items.begin(), changes, back_inserter(event_result), [](auto item){ return item; });
  
                  if (event_result.size() > 0) {
                      for (auto item: events) 
                          item->send_event();
                      service_items = new_service_items;
                  }
              }
          });
          thread.detach();        
      }
      return Number::New(info.Env(), (long long)event);
  }
  
  auto UnregisterServiceEvents(const CallbackInfo& info) -> Value { 
      auto event = reinterpret_cast<Events*>(info[0].As<Number>().Int64Value());
      auto it = find(events.begin(), events.end(), event);
      events.erase(it);
      // TODO: race condition (thread)
      event->stop();
  
      if (events.empty()) 
          CloseServiceHandle(service_handle);
      return Value(); 
  }
  
  void service_action(const Env& env, const wstring& name, function<void(SC_HANDLE handle)> action) {
      auto sc_handle = OpenSCManagerW(nullptr, nullptr, GENERIC_READ);
      auto service =  OpenServiceW(sc_handle, name.c_str(), GENERIC_READ | SERVICE_START | SERVICE_STOP);
      auto error = GetLastError();
      if (!service && error == 5) {
          Error::New(env, "5").ThrowAsJavaScriptException();
          CloseServiceHandle(sc_handle);
      }
      action(service);
      CloseServiceHandle(service);
      CloseServiceHandle(sc_handle);
  }
  
  auto StartService1(const CallbackInfo& info) -> Value { 
      auto name = info[0].As<WString>().WValue();
      service_action(info.Env(), name, [](SC_HANDLE service){ 
          auto res = StartServiceW(service, 0, nullptr); 
          auto error = GetLastError();
      });
      return Value(); 
  }
  
  Napi::Value StopService(const Napi::CallbackInfo& info) {
      auto name = info[0].As<WString>().WValue();
      service_action(info.Env(), name, [](SC_HANDLE service){ 
          SERVICE_STATUS srv_stat{0};
          ControlService(service, SERVICE_CONTROL_STOP, &srv_stat); 
      });
      return Value(); 
  }
  
  void Events::start() { Initialize(); }
  
  void Events::stop() { 
      Dispose();
  }
  
  void Events::OnEvent() { 
      HandleScope scope(callback.Env());
      vector<napi_value> args;
  
      auto result = Array::New(callback.Env(), event_result.size());
      int i{0};
      for (auto item: event_result) {
          auto obj = Object::New(callback.Env());
  
          obj.Set("name", WString::New(callback.Env(), item.name));
          obj.Set("displayName", WString::New(callback.Env(), item.display_name));
          obj.Set("status", Number::New(callback.Env(), item.status));
  
          result.Set(i++, obj);
      }
  
      args.push_back(result);
      callback.Call(args);
  }
  
  