/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

/**
 * This file exists so that LaunchModernSettingsDialogDefaultApps can be called
 * without linking to libxul.
 */
#include "WindowsDefaultBrowser.h"

#include "city.h"
#include "mozilla/ArrayUtils.h"
#include "mozilla/UniquePtr.h"
#include "mozilla/WindowsVersion.h"
#include "mozilla/WinHeaderOnlyUtils.h"

// Needed for access to IApplicationActivationManager
// This must be before any other includes that might include shlobj.h
#ifdef _WIN32_WINNT
#  undef _WIN32_WINNT
#endif
#define _WIN32_WINNT 0x0600
#define INITGUID
#undef NTDDI_VERSION
#define NTDDI_VERSION NTDDI_WIN8
#include <shlobj.h>

#include <lm.h>
#include <shellapi.h>
#include <shlwapi.h>
#include <wchar.h>
#include <windows.h>

#define APP_REG_NAME_BASE L"Firefox-"

static bool IsWindowsLogonConnected() {
  WCHAR userName[UNLEN + 1];
  DWORD size = mozilla::ArrayLength(userName);
  if (!GetUserNameW(userName, &size)) {
    return false;
  }

  LPUSER_INFO_24 info;
  if (NetUserGetInfo(nullptr, userName, 24, (LPBYTE*)&info) != NERR_Success) {
    return false;
  }
  bool connected = info->usri24_internet_identity;
  NetApiBufferFree(info);

  return connected;
}

static bool SettingsAppBelievesConnected() {
  const wchar_t* keyPath = L"SOFTWARE\\Microsoft\\Windows\\Shell\\Associations";
  const wchar_t* valueName = L"IsConnectedAtLogon";

  uint32_t value = 0;
  DWORD size = sizeof(uint32_t);
  LSTATUS ls = RegGetValueW(HKEY_CURRENT_USER, keyPath, valueName, RRF_RT_ANY,
                            nullptr, &value, &size);
  if (ls != ERROR_SUCCESS) {
    return false;
  }

  return !!value;
}

bool GetAppRegName(mozilla::UniquePtr<wchar_t[]>& aAppRegName) {
  mozilla::UniquePtr<wchar_t[]> appDirStr;
  bool success = GetInstallDirectory(appDirStr);
  if (!success) {
    return success;
  }

  uint64_t hash = CityHash64(reinterpret_cast<char*>(appDirStr.get()),
                             wcslen(appDirStr.get()) * sizeof(wchar_t));

  const wchar_t* format = L"%s%I64X";
  int bufferSize = _scwprintf(format, APP_REG_NAME_BASE, hash);
  ++bufferSize;  // Extra byte for terminating null
  aAppRegName = mozilla::MakeUnique<wchar_t[]>(bufferSize);

  _snwprintf_s(aAppRegName.get(), bufferSize, _TRUNCATE, format,
               APP_REG_NAME_BASE, hash);

  return success;
}

bool LaunchControlPanelDefaultPrograms() {
  // Build the path control.exe path safely
  WCHAR controlEXEPath[MAX_PATH + 1] = {'\0'};
  if (!GetSystemDirectoryW(controlEXEPath, MAX_PATH)) {
    return false;
  }
  LPCWSTR controlEXE = L"control.exe";
  if (wcslen(controlEXEPath) + wcslen(controlEXE) >= MAX_PATH) {
    return false;
  }
  if (!PathAppendW(controlEXEPath, controlEXE)) {
    return false;
  }

  const wchar_t* paramFormat =
      L"control.exe /name Microsoft.DefaultPrograms "
      L"/page pageDefaultProgram\\pageAdvancedSettings?pszAppName=%s";
  mozilla::UniquePtr<wchar_t[]> appRegName;
  GetAppRegName(appRegName);
  int bufferSize = _scwprintf(paramFormat, appRegName.get());
  ++bufferSize;  // Extra byte for terminating null
  mozilla::UniquePtr<wchar_t[]> params =
      mozilla::MakeUnique<wchar_t[]>(bufferSize);
  _snwprintf_s(params.get(), bufferSize, _TRUNCATE, paramFormat,
               appRegName.get());

  STARTUPINFOW si = {sizeof(si), 0};
  si.dwFlags = STARTF_USESHOWWINDOW;
  si.wShowWindow = SW_SHOWDEFAULT;
  PROCESS_INFORMATION pi = {0};
  if (!CreateProcessW(controlEXEPath, params.get(), nullptr, nullptr, FALSE, 0,
                      nullptr, nullptr, &si, &pi)) {
    return false;
  }
  CloseHandle(pi.hProcess);
  CloseHandle(pi.hThread);

  return true;
}

static bool IsAppRegistered(HKEY rootKey, const wchar_t* appRegName) {
  const wchar_t* keyPath = L"Software\\RegisteredApplications";

  DWORD size = sizeof(uint32_t);
  LSTATUS ls = RegGetValueW(rootKey, keyPath, appRegName, RRF_RT_ANY, nullptr,
                            nullptr, &size);
  return ls == ERROR_SUCCESS;
}

static bool LaunchMsSettingsProtocol() {
  mozilla::UniquePtr<wchar_t[]> params = nullptr;
  if (mozilla::HasPackageIdentity()) {
    mozilla::UniquePtr<wchar_t[]> packageFamilyName =
        mozilla::GetPackageFamilyName();
    if (packageFamilyName) {
      const wchar_t* paramFormat =
          L"ms-settings:defaultapps?registeredAUMID=%s!App";
      int bufferSize = _scwprintf(paramFormat, packageFamilyName.get());
      ++bufferSize;  // Extra byte for terminating null
      params = mozilla::MakeUnique<wchar_t[]>(bufferSize);
      _snwprintf_s(params.get(), bufferSize, _TRUNCATE, paramFormat,
                   packageFamilyName.get());
    }
  }
  if (!params) {
    mozilla::UniquePtr<wchar_t[]> appRegName;
    GetAppRegName(appRegName);
    const wchar_t* paramFormat =
        IsAppRegistered(HKEY_CURRENT_USER, appRegName.get()) ||
                !IsAppRegistered(HKEY_LOCAL_MACHINE, appRegName.get())
            ? L"ms-settings:defaultapps?registeredAppUser=%s"
            : L"ms-settings:defaultapps?registeredAppMachine=%s";
    int bufferSize = _scwprintf(paramFormat, appRegName.get());
    ++bufferSize;  // Extra byte for terminating null
    params = mozilla::MakeUnique<wchar_t[]>(bufferSize);
    _snwprintf_s(params.get(), bufferSize, _TRUNCATE, paramFormat,
                 appRegName.get());
  }

  SHELLEXECUTEINFOW seinfo = {sizeof(seinfo)};
  seinfo.lpFile = params.get();
  seinfo.nShow = SW_SHOWNORMAL;
  return ShellExecuteExW(&seinfo);
}

bool LaunchModernSettingsDialogDefaultApps() {
  if (mozilla::IsWin11OrLater()) {
    return LaunchMsSettingsProtocol();
  }

  if (!mozilla::IsWindowsBuildOrLater(14965) && !IsWindowsLogonConnected() &&
      SettingsAppBelievesConnected()) {
    // Use the classic Control Panel to work around a bug of older
    // builds of Windows 10.
    return LaunchControlPanelDefaultPrograms();
  }

  IApplicationActivationManager* pActivator;
  HRESULT hr = CoCreateInstance(
      CLSID_ApplicationActivationManager, nullptr, CLSCTX_INPROC,
      IID_IApplicationActivationManager, (void**)&pActivator);

  if (SUCCEEDED(hr)) {
    DWORD pid;
    hr = pActivator->ActivateApplication(
        L"windows.immersivecontrolpanel_cw5n1h2txyewy"
        L"!microsoft.windows.immersivecontrolpanel",
        L"page=SettingsPageAppsDefaults", AO_NONE, &pid);
    if (SUCCEEDED(hr)) {
      // Do not check error because we could at least open
      // the "Default apps" setting.
      pActivator->ActivateApplication(
          L"windows.immersivecontrolpanel_cw5n1h2txyewy"
          L"!microsoft.windows.immersivecontrolpanel",
          L"page=SettingsPageAppsDefaults"
          L"&target=SystemSettings_DefaultApps_Browser",
          AO_NONE, &pid);
    }
    pActivator->Release();
    return SUCCEEDED(hr);
  }
  return true;
}
