text: #include <vcpkg/base/chrono.h>
  #include <vcpkg/base/files.h>
  #include <vcpkg/base/hash.h>
  #include <vcpkg/base/json.h>
  #include <vcpkg/base/strings.h>
  #include <vcpkg/base/system.process.h>
  
  #include <vcpkg/commands.h>
  #include <vcpkg/commands.version.h>
  #include <vcpkg/metrics.h>
  
  #if defined(_WIN32)
  #pragma comment(lib, "version")
  #pragma comment(lib, "winhttp")
  #endif
  
  namespace vcpkg::Metrics
  {
      Util::LockGuarded<Metrics> g_metrics;
  
      static std::string get_current_date_time()
      {
          auto maybe_time = Chrono::CTime::get_current_date_time();
          if (auto ptime = maybe_time.get())
          {
              return ptime->to_string();
          }
  
          return "";
      }
  
      struct append_hexits
      {
          constexpr static char hex[17] = "0123456789abcdef";
          void operator()(std::string& res, std::uint8_t bits) const
          {
              res.push_back(hex[(bits >> 4) & 0x0F]);
              res.push_back(hex[(bits >> 0) & 0x0F]);
          }
      };
      constexpr char append_hexits::hex[17];
  
      // note: this ignores the bits of these numbers that would be where format and variant go
      static std::string uuid_of_integers(uint64_t top, uint64_t bottom)
      {
          // uuid_field_size in bytes, not hex characters
          constexpr size_t uuid_top_field_size[] = {4, 2, 2};
          constexpr size_t uuid_bottom_field_size[] = {2, 6};
  
          // uuid_field_size in hex characters, not bytes
          constexpr size_t uuid_size = 8 + 1 + 4 + 1 + 4 + 1 + 4 + 1 + 12;
  
          constexpr static append_hexits write_byte;
  
          // set the version bits to 4
          top &= 0xFFFF'FFFF'FFFF'0FFFULL;
          top |= 0x0000'0000'0000'4000ULL;
  
          // set the variant bits to 2 (variant one)
          bottom &= 0x3FFF'FFFF'FFFF'FFFFULL;
          bottom |= 0x8000'0000'0000'0000ULL;
  
          std::string res;
          res.reserve(uuid_size);
  
          bool first = true;
          size_t start_byte = 0;
          for (auto field_size : uuid_top_field_size)
          {
              if (!first)
              {
                  res.push_back('-');
              }
              first = false;
              for (size_t i = start_byte; i < start_byte + field_size; ++i)
              {
                  auto shift = 64 - (i + 1) * 8;
                  write_byte(res, (top >> shift) & 0xFF);
              }
              start_byte += field_size;
          }
  
          start_byte = 0;
          for (auto field_size : uuid_bottom_field_size)
          {
              res.push_back('-');
              for (size_t i = start_byte; i < start_byte + field_size; ++i)
              {
                  auto shift = 64 - (i + 1) * 8;
                  write_byte(res, (bottom >> shift) & 0xFF);
              }
              start_byte += field_size;
          }
  
          return res;
      }
  
      // UUID format version 4, variant 1
      // http://en.wikipedia.org/wiki/Universally_unique_identifier
      // [0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}
      static std::string generate_random_UUID()
      {
          std::random_device rnd{};
          std::uniform_int_distribution<std::uint64_t> uid{};
          return uuid_of_integers(uid(rnd), uid(rnd));
      }
  
      static const std::string& get_session_id()
      {
          static const std::string ID = generate_random_UUID();
          return ID;
      }
  
      static std::string get_os_version_string()
      {
  #if defined(_WIN32)
          std::wstring path;
          path.resize(MAX_PATH);
          const auto n = GetSystemDirectoryW(&path[0], static_cast<UINT>(path.size()));
          path.resize(n);
          path += L"\\kernel32.dll";
  
          const auto versz = GetFileVersionInfoSizeW(path.c_str(), nullptr);
          if (versz == 0) return "";
  
          std::vector<char> verbuf;
          verbuf.resize(versz);
  
          if (!GetFileVersionInfoW(path.c_str(), 0, static_cast<DWORD>(verbuf.size()), &verbuf[0])) return "";
  
          void* rootblock;
          UINT rootblocksize;
          if (!VerQueryValueW(&verbuf[0], L"\\", &rootblock, &rootblocksize)) return "";
  
          auto rootblock_ffi = static_cast<VS_FIXEDFILEINFO*>(rootblock);
  
          return Strings::format("%d.%d.%d",
                                 static_cast<int>(HIWORD(rootblock_ffi->dwProductVersionMS)),
                                 static_cast<int>(LOWORD(rootblock_ffi->dwProductVersionMS)),
                                 static_cast<int>(HIWORD(rootblock_ffi->dwProductVersionLS)));
  #else
          return "unknown";
  #endif
      }
  
      struct MetricMessage
      {
          std::string user_id = generate_random_UUID();
          std::string user_timestamp;
          std::string timestamp = get_current_date_time();
  
          Json::Object properties;
          Json::Object measurements;
  
          Json::Array buildtime_names;
          Json::Array buildtime_times;
  
          void track_property(const std::string& name, const std::string& value)
          {
              properties.insert_or_replace(name, Json::Value::string(value));
          }
  
          void track_metric(const std::string& name, double value)
          {
              measurements.insert_or_replace(name, Json::Value::number(value));
          }
  
          void track_buildtime(const std::string& name, double value)
          {
              buildtime_names.push_back(Json::Value::string(name));
              buildtime_times.push_back(Json::Value::number(value));
          }
          void track_feature(const std::string& name, bool value)
          {
              properties.insert("feature-flag-" + name, Json::Value::boolean(value));
          }
  
          std::string format_event_data_template() const
          {
              auto props_plus_buildtimes = properties;
              if (buildtime_names.size() > 0)
              {
                  props_plus_buildtimes.insert("buildnames_1", buildtime_names);
                  props_plus_buildtimes.insert("buildtimes", buildtime_times);
              }
  
              Json::Array arr = Json::Array();
              Json::Object& obj = arr.push_back(Json::Object());
  
              obj.insert("ver", Json::Value::integer(1));
              obj.insert("name", Json::Value::string("Microsoft.ApplicationInsights.Event"));
              obj.insert("time", Json::Value::string(timestamp));
              obj.insert("sampleRate", Json::Value::number(100.0));
              obj.insert("seq", Json::Value::string("0:0"));
              obj.insert("iKey", Json::Value::string("b4e88960-4393-4dd9-ab8e-97e8fe6d7603"));
              obj.insert("flags", Json::Value::integer(0));
  
              {
                  Json::Object& tags = obj.insert("tags", Json::Object());
  
                  tags.insert("ai.device.os", Json::Value::string("Other"));
  
                  const char* os_name =
  #if defined(_WIN32)
                      "Windows";
  #elif defined(__APPLE__)
                      "OSX";
  #elif defined(__linux__)
                      "Linux";
  #elif defined(__FreeBSD__)
                      "FreeBSD";
  #elif defined(__unix__)
                      "Unix";
  #else
                      "Other";
  #endif
  
                  tags.insert("ai.device.osVersion",
                              Json::Value::string(Strings::format("%s-%s", os_name, get_os_version_string())));
                  tags.insert("ai.session.id", Json::Value::string(get_session_id()));
                  tags.insert("ai.user.id", Json::Value::string(user_id));
                  tags.insert("ai.user.accountAcquisitionDate", Json::Value::string(user_timestamp));
              }
  
              {
                  Json::Object& data = obj.insert("data", Json::Object());
  
                  data.insert("baseType", Json::Value::string("EventData"));
                  Json::Object& base_data = data.insert("baseData", Json::Object());
  
                  base_data.insert("ver", Json::Value::integer(2));
                  base_data.insert("name", Json::Value::string("commandline_test7"));
                  base_data.insert("properties", std::move(props_plus_buildtimes));
                  base_data.insert("measurements", measurements);
              }
  
              return Json::stringify(arr, vcpkg::Json::JsonStyle());
          }
      };
  
      static MetricMessage g_metricmessage;
      static bool g_should_send_metrics =
  #if defined(NDEBUG)
          true
  #else
          false
  #endif
          ;
      static bool g_should_print_metrics = false;
      static bool g_metrics_disabled = false;
  
      std::string get_MAC_user()
      {
  #if defined(_WIN32)
          if (!g_metrics.lock()->metrics_enabled())
          {
              return "{}";
          }
  
          auto getmac = System::cmd_execute_and_capture_output(System::Command("getmac"));
  
          if (getmac.exit_code != 0) return "0";
  
          std::regex mac_regex("([a-fA-F0-9]{2}(-[a-fA-F0-9]{2}){5})");
          std::sregex_iterator next(getmac.output.begin(), getmac.output.end(), mac_regex);
          std::sregex_iterator last;
  
          while (next != last)
          {
              const auto match = *next;
              if (match[0] != "00-00-00-00-00-00")
              {
                  return vcpkg::Hash::get_string_hash(match[0].str(), Hash::Algorithm::Sha256);
              }
              ++next;
          }
  
          return "0";
  #else
          return "{}";
  #endif
      }
  
      void Metrics::set_user_information(const std::string& user_id, const std::string& first_use_time)
      {
          g_metricmessage.user_id = user_id;
          g_metricmessage.user_timestamp = first_use_time;
      }
  
      void Metrics::init_user_information(std::string& user_id, std::string& first_use_time)
      {
          user_id = generate_random_UUID();
          first_use_time = get_current_date_time();
      }
  
      void Metrics::set_send_metrics(bool should_send_metrics) { g_should_send_metrics = should_send_metrics; }
  
      void Metrics::set_print_metrics(bool should_print_metrics) { g_should_print_metrics = should_print_metrics; }
  
      void Metrics::set_disabled(bool disabled) { g_metrics_disabled = disabled; }
  
      bool Metrics::metrics_enabled() { return !g_metrics_disabled; }
  
      void Metrics::track_metric(const std::string& name, double value)
      {
          if (!metrics_enabled())
          {
              return;
          }
          g_metricmessage.track_metric(name, value);
      }
  
      void Metrics::track_buildtime(const std::string& name, double value)
      {
          if (!metrics_enabled())
          {
              return;
          }
          g_metricmessage.track_buildtime(name, value);
      }
  
      void Metrics::track_property(const std::string& name, const std::string& value)
      {
          if (!metrics_enabled())
          {
              return;
          }
          g_metricmessage.track_property(name, value);
      }
  
      void Metrics::track_feature(const std::string& name, bool value)
      {
          if (!metrics_enabled())
          {
              return;
          }
          g_metricmessage.track_feature(name, value);
      }
  
      void Metrics::upload(const std::string& payload)
      {
          if (!metrics_enabled())
          {
              return;
          }
  
  #if defined(_WIN32)
          HINTERNET connect = nullptr, request = nullptr;
          BOOL results = FALSE;
  
          const HINTERNET session = WinHttpOpen(
              L"vcpkg/1.0", WINHTTP_ACCESS_TYPE_DEFAULT_PROXY, WINHTTP_NO_PROXY_NAME, WINHTTP_NO_PROXY_BYPASS, 0);
  
          unsigned long secure_protocols = WINHTTP_FLAG_SECURE_PROTOCOL_TLS1_2;
          if (session && WinHttpSetOption(session, WINHTTP_OPTION_SECURE_PROTOCOLS, &secure_protocols, sizeof(DWORD)))
          {
              connect = WinHttpConnect(session, L"dc.services.visualstudio.com", INTERNET_DEFAULT_HTTPS_PORT, 0);
          }
  
          if (connect)
          {
              request = WinHttpOpenRequest(connect,
                                           L"POST",
                                           L"/v2/track",
                                           nullptr,
                                           WINHTTP_NO_REFERER,
                                           WINHTTP_DEFAULT_ACCEPT_TYPES,
                                           WINHTTP_FLAG_SECURE);
          }
  
          if (request)
          {
              if (MAXDWORD <= payload.size()) abort();
              std::wstring hdrs = L"Content-Type: application/json\r\n";
              std::string& p = const_cast<std::string&>(payload);
              results = WinHttpSendRequest(request,
                                           hdrs.c_str(),
                                           static_cast<DWORD>(hdrs.size()),
                                           static_cast<void*>(&p[0]),
                                           static_cast<DWORD>(payload.size()),
                                           static_cast<DWORD>(payload.size()),
                                           0);
          }
  
          if (results)
          {
              results = WinHttpReceiveResponse(request, nullptr);
          }
  
          DWORD http_code = 0, junk = sizeof(DWORD);
  
          if (results)
          {
              results = WinHttpQueryHeaders(request,
                                            WINHTTP_QUERY_STATUS_CODE | WINHTTP_QUERY_FLAG_NUMBER,
                                            nullptr,
                                            &http_code,
                                            &junk,
                                            WINHTTP_NO_HEADER_INDEX);
          }
  
          std::vector<char> response_buffer;
          if (results)
          {
              DWORD available_data = 0, read_data = 0, total_data = 0;
              while ((results = WinHttpQueryDataAvailable(request, &available_data)) == TRUE && available_data > 0)
              {
                  response_buffer.resize(response_buffer.size() + available_data);
  
                  results = WinHttpReadData(request, &response_buffer.data()[total_data], available_data, &read_data);
  
                  if (!results)
                  {
                      break;
                  }
  
                  total_data += read_data;
  
                  response_buffer.resize(total_data);
              }
          }
  
          if (!results)
          {
  #ifndef NDEBUG
              __debugbreak();
              auto err = GetLastError();
              std::cerr << "[DEBUG] failed to connect to server: " << err << "\n";
  #endif // NDEBUG
          }
  
          if (request) WinHttpCloseHandle(request);
          if (connect) WinHttpCloseHandle(connect);
          if (session) WinHttpCloseHandle(session);
  #else // ^^^ _WIN32 // !_WIN32 vvv
          (void)payload;
  #endif // ^^^ !_WIN32
      }
  
      void Metrics::flush(Files::Filesystem& fs)
      {
          if (!metrics_enabled())
          {
              return;
          }
  
          const std::string payload = g_metricmessage.format_event_data_template();
          if (g_should_print_metrics) std::cerr << payload << "\n";
          if (!g_should_send_metrics) return;
  
  #if defined(_WIN32)
          wchar_t temp_folder[MAX_PATH];
          GetTempPathW(MAX_PATH, temp_folder);
  
          const fs::path temp_folder_path = fs::path(temp_folder) / "vcpkg";
          const fs::path temp_folder_path_exe =
              temp_folder_path / Strings::format("vcpkg-%s.exe", Commands::Version::base_version());
  #endif
  
          std::error_code ec;
  #if defined(_WIN32)
          fs.create_directories(temp_folder_path, ec);
          if (ec) return;
          fs.copy_file(
              System::get_exe_path_of_current_process(), temp_folder_path_exe, fs::copy_options::skip_existing, ec);
          if (ec) return;
  #else
          if (!fs.exists("/tmp")) return;
          const fs::path temp_folder_path = "/tmp/vcpkg";
          fs.create_directory(temp_folder_path, ignore_errors);
  #endif
          const fs::path vcpkg_metrics_txt_path = temp_folder_path / ("vcpkg" + generate_random_UUID() + ".txt");
          fs.write_contents(vcpkg_metrics_txt_path, payload, ec);
          if (ec) return;
  
  #if defined(_WIN32)
          System::Command builder;
          builder.path_arg(temp_folder_path_exe);
          builder.string_arg("x-upload-metrics");
          builder.path_arg(vcpkg_metrics_txt_path);
          System::cmd_execute_background(builder);
  #else
          // TODO: convert to cmd_execute_background or something.
          auto curl = System::Command("curl")
                          .string_arg("https://dc.services.visualstudio.com/v2/track")
                          .string_arg("--max-time")
                          .string_arg("3")
                          .string_arg("-H")
                          .string_arg("Content-Type: application/json")
                          .string_arg("-X")
                          .string_arg("POST")
                          .string_arg("--tlsv1.2")
                          .string_arg("--data")
                          .string_arg(Strings::concat("@", fs::u8string(vcpkg_metrics_txt_path)))
                          .raw_arg(">/dev/null")
                          .raw_arg("2>&1");
          auto remove = System::Command("rm").path_arg(vcpkg_metrics_txt_path);
          System::Command cmd_line;
          cmd_line.raw_arg("(").raw_arg(curl.command_line()).raw_arg(";").raw_arg(remove.command_line()).raw_arg(") &");
          System::cmd_execute_clean(cmd_line);
  #endif
      }
  }
  