// Copyright 2025 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "ui/display/win/screen_win_headless.h"

#include <windows.h>

#include <vector>

#include "base/compiler_specific.h"
#include "base/containers/span.h"
#include "base/notreached.h"
#include "base/numerics/safe_conversions.h"
#include "base/strings/strcat.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/string_number_conversions_win.h"
#include "base/strings/string_util.h"
#include "base/strings/string_util_win.h"
#include "components/headless/screen_info/headless_screen_info.h"
#include "ui/display/display_finder.h"
#include "ui/display/display_list.h"
#include "ui/display/headless/headless_screen_util.h"
#include "ui/display/win/display_info.h"
#include "ui/display/win/dpi.h"
#include "ui/display/win/screen_win_display.h"
#include "ui/gfx/geometry/insets.h"
#include "ui/gfx/geometry/insets_conversions.h"
#include "ui/gfx/geometry/insets_f.h"

namespace display::win {

namespace {

// Default values used to construct internal::DisplayInfo.
constexpr float kSdrWhiteLevel = 200.0;
constexpr float kDisplayFrequency = 60.0;
constexpr float kTextScaleMultiplier = 1.0f;

// Headless display device names are fakes that look similar to the real display
// device names.
constexpr WCHAR kHeadlessDisplayDeviceNamePrefix[] = LR"(\\.\HEADLESS_DISPLAY)";

std::wstring GetHeadlessDisplayDeviceNameFromDisplayId(int64_t id) {
  return base::StrCat(
      {kHeadlessDisplayDeviceNamePrefix, base::NumberToWString(id)});
}

int64_t GetHeadlessDisplayIdFromMonitorInfo(const MONITORINFOEX& monitor_info) {
  CHECK(base::StartsWith(monitor_info.szDevice,
                         kHeadlessDisplayDeviceNamePrefix));
  int64_t id;
  CHECK(base::StringToInt64(
      &monitor_info.szDevice[std::size(kHeadlessDisplayDeviceNamePrefix) - 1],
      &id));

  return id;
}

gfx::Vector2dF GetDisplayPhysicalPixelsPerInch(float device_scaling_factor) {
  const int dpi = GetDPIFromScalingFactor(device_scaling_factor);
  return gfx::Vector2dF(dpi, dpi);
}

DISPLAYCONFIG_VIDEO_OUTPUT_TECHNOLOGY GetOutputTechnology(bool is_internal) {
  return is_internal ? DISPLAYCONFIG_OUTPUT_TECHNOLOGY_INTERNAL
                     : DISPLAYCONFIG_OUTPUT_TECHNOLOGY_OTHER;
}

void SetHeadlessDisplayDeviceName(MONITORINFOEX& monitor_info,
                                  int64_t display_id) {
  const std::wstring device_name =
      GetHeadlessDisplayDeviceNameFromDisplayId(display_id);
  CHECK_LT(device_name.length() + 1, std::size(monitor_info.szDevice));

  base::span<WCHAR> device_name_buf = monitor_info.szDevice;
  std::copy(device_name.begin(), device_name.end(), device_name_buf.begin());
  device_name_buf[device_name.length()] = L'\0';
}

void UpdateMonitorInfoBoundsAndWorkArea(MONITORINFOEX& monitor_info,
                                        const Display& display) {
  // Display maintains its bounds scaled, so convert it to physical pixels.
  gfx::Rect bounds_in_pixels = display.bounds();
  if (display.device_scale_factor() != 1.0f) {
    bounds_in_pixels = gfx::ScaleToEnclosingRect(bounds_in_pixels,
                                                 display.device_scale_factor());
  }
  monitor_info.rcMonitor = bounds_in_pixels.ToRECT();

  // Display maintains its work area scaled, so convert it to physical pixels.
  gfx::Insets work_area_insets_pixels = display.GetWorkAreaInsets();
  if (display.device_scale_factor() != 1.0f) {
    gfx::InsetsF insets =
        static_cast<gfx::InsetsF>(display.GetWorkAreaInsets());
    insets.Scale(display.device_scale_factor());
    work_area_insets_pixels = gfx::ToCeiledInsets(insets);
  }

  gfx::Rect work_area_in_pixels = bounds_in_pixels;
  work_area_in_pixels.Inset(work_area_insets_pixels);
  monitor_info.rcWork = work_area_in_pixels.ToRECT();
}

}  // namespace

ScreenWinHeadless::ScreenWinHeadless(
    const std::vector<headless::HeadlessScreenInfo>& screen_infos)
    : ScreenWin(/*initialize_from_system=*/false) {
  CHECK(!screen_infos.empty());

  UpdateFromDisplayInfos(DisplayInfosFromScreenInfo(screen_infos));

  HeadlessScreenManager::Get()->SetDelegate(this);
}

ScreenWinHeadless::~ScreenWinHeadless() {
  HeadlessScreenManager::Get()->SetDelegate(nullptr);
}

int64_t ScreenWinHeadless::GetDisplayIdFromWindow(HWND hwnd,
                                                  DWORD default_options) {
  if (auto monitor_info = MonitorInfoFromWindow(hwnd, default_options)) {
    return GetDisplayIdFromMonitorInfo(monitor_info.value());
  }

  return kInvalidDisplayId;
}

int64_t ScreenWinHeadless::GetDisplayIdFromScreenRect(
    const gfx::Rect& screen_rect) {
  if (auto monitor_info = MonitorInfoFromScreenRect(screen_rect)) {
    return GetDisplayIdFromMonitorInfo(monitor_info.value());
  }

  return GetPrimaryDisplay().id();
}

int ScreenWinHeadless::GetSystemMetricsForDisplayId(int64_t id, int metric) {
  const Display display = GetScreenWinDisplayWithDisplayId(id).display();
  return base::ClampRound(::GetSystemMetrics(metric) *
                          display.device_scale_factor());
}

void ScreenWinHeadless::SetCursorScreenPointForTesting(
    const gfx::Point& point) {
  cursor_screen_point_ = point;
}

gfx::Point ScreenWinHeadless::GetCursorScreenPoint() {
  return cursor_screen_point_;
}

bool ScreenWinHeadless::IsWindowUnderCursor(gfx::NativeWindow window) {
  return GetWindowAtScreenPoint(GetCursorScreenPoint()) == window;
}

gfx::NativeWindow ScreenWinHeadless::GetWindowAtScreenPoint(
    const gfx::Point& point) {
  return GetNativeWindowAtScreenPoint(point, std::set<gfx::NativeWindow>());
}

gfx::NativeWindow ScreenWinHeadless::GetLocalProcessWindowAtPoint(
    const gfx::Point& point,
    const std::set<gfx::NativeWindow>& ignore) {
  return GetRootWindow(GetNativeWindowAtScreenPoint(point, ignore));
}

int ScreenWinHeadless::GetNumDisplays() const {
  return GetAllDisplays().size();
}

const std::vector<Display>& ScreenWinHeadless::GetAllDisplays() const {
  return ScreenWin::GetAllDisplays();
}

Display ScreenWinHeadless::GetDisplayNearestWindow(
    gfx::NativeWindow window) const {
  if (window) {
    return GetDisplayFromScreenRect(GetNativeWindowBoundsInScreen(window));
  }

  return GetPrimaryDisplay();
}

Display ScreenWinHeadless::GetDisplayNearestPoint(
    const gfx::Point& point) const {
  return GetDisplayFromScreenPoint(point);
}

Display ScreenWinHeadless::GetDisplayMatching(
    const gfx::Rect& match_rect) const {
  return GetDisplayFromScreenRect(match_rect);
}

Display ScreenWinHeadless::GetPrimaryDisplay() const {
  // In most cases the primary display is the very first display in the list and
  // the display count is low, so linear search should not be a problem here.
  for (const Display& display : GetAllDisplays()) {
    if (display.id() == primary_display_id_) {
      return display;
    }
  }

  return Display::GetDefaultDisplay();
}

HMONITOR ScreenWinHeadless::HMONITORFromScreenPoint(
    const gfx::Point& screen_point) const {
  NOTREACHED();
}

HMONITOR ScreenWinHeadless::HMONITORFromScreenRect(
    const gfx::Rect& screen_rect) const {
  NOTREACHED();
}

HMONITOR ScreenWinHeadless::HMONITORFromWindow(HWND hwnd,
                                               DWORD default_options) const {
  NOTREACHED();
}

std::optional<MONITORINFOEX> ScreenWinHeadless::MonitorInfoFromScreenPoint(
    const gfx::Point& screen_point) const {
  // ScreenWin::MonitorInfoFromScreenPoint() uses Win32 ::MonitorFromPoint()
  // with MONITOR_DEFAULTTONEAREST flag.
  if (const Display* display =
          FindDisplayNearestPoint(GetAllDisplays(), screen_point)) {
    return GetMONITORINFOFromDisplayId(display->id());
  }

  return std::nullopt;
}

std::optional<MONITORINFOEX> ScreenWinHeadless::MonitorInfoFromHMONITOR(
    HMONITOR monitor) const {
  return std::nullopt;
}

gfx::Rect ScreenWinHeadless::ScreenToDIPRectInWindow(
    gfx::NativeWindow window,
    const gfx::Rect& screen_rect) const {
  // The base class implementation does the right thing, but we want this to be
  // exposed publicly as the rest of display::Screen overrides.
  return ScreenWin::ScreenToDIPRectInWindow(window, screen_rect);
}

gfx::Rect ScreenWinHeadless::DIPToScreenRectInWindow(
    gfx::NativeWindow window,
    const gfx::Rect& dip_rect) const {
  // The base class implementation does the right thing, but we want this to be
  // exposed publicly as the rest of display::Screen overrides.
  return ScreenWin::DIPToScreenRectInWindow(window, dip_rect);
}

bool ScreenWinHeadless::IsHeadless() const {
  return true;
}

std::optional<MONITORINFOEX> ScreenWinHeadless::MonitorInfoFromScreenRect(
    const gfx::Rect& screen_rect) const {
  // ScreenWin::MonitorInfoFromScreenRect() uses Win32 ::MonitorFromRect() with
  // MONITOR_DEFAULTTONEAREST flag.
  if (const Display* display =
          FindDisplayWithBiggestIntersection(GetAllDisplays(), screen_rect)) {
    return GetMONITORINFOFromDisplayId(display->id());
  }

  if (const Display* display = FindDisplayNearestPoint(
          GetAllDisplays(), screen_rect.CenterPoint())) {
    return GetMONITORINFOFromDisplayId(display->id());
  }

  return std::nullopt;
}

std::optional<MONITORINFOEX> ScreenWinHeadless::MonitorInfoFromWindow(
    HWND hwnd,
    DWORD default_options) const {
  CHECK(hwnd);
  const gfx::Rect bounds = GetHeadlessWindowBounds(hwnd);

  // ScreenWin::MonitorInfoFromWindow() calls Win32 ::MonitorFromWindow() so
  // replicate its behavior according to
  // https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-monitorfromwindow
  if (const Display* display =
          FindDisplayWithBiggestIntersection(GetAllDisplays(), bounds)) {
    return GetMONITORINFOFromDisplayId(display->id());
  }

  if (default_options == MONITOR_DEFAULTTONEAREST) {
    if (const Display* display =
            FindDisplayNearestPoint(GetAllDisplays(), bounds.CenterPoint())) {
      return GetMONITORINFOFromDisplayId(display->id());
    }
  } else if (default_options == MONITOR_DEFAULTTOPRIMARY) {
    return GetMONITORINFOFromDisplayId(GetPrimaryDisplay().id());
  }

  return std::nullopt;
}

HWND ScreenWinHeadless::GetRootWindow(HWND hwnd) const {
  // Headless windows don't have hierarchy, so return self.
  return hwnd;
}

int64_t ScreenWinHeadless::GetDisplayIdFromMonitorInfo(
    const MONITORINFOEX& monitor_info) const {
  // This will crash if called with the real Windows monitor info.
  return GetHeadlessDisplayIdFromMonitorInfo(monitor_info);
}

void ScreenWinHeadless::UpdateAllDisplaysAndNotify() {
  // Ignore all display update requests because the underlying implementation
  // requests display infos from the system and overrides headless screen
  // configuration. Headless screen configuration is defined in ctor and never
  // changes.
}

void ScreenWinHeadless::UpdateAllDisplaysIfPrimaryMonitorChanged() {
  // Headless primary monitor never changes, so intercept and ignore.
}

void ScreenWinHeadless::OnColorProfilesChanged() {
  // Just ignore as we don't expect any on the fly color profile changes in
  // headless mode.
}

void ScreenWinHeadless::SetRequestHDRStatusCallback(
    RequestHDRStatusCallback request_hdr_status_callback) {
  // Ignore all HDR Status requests in headless mode.
}

void ScreenWinHeadless::SetDXGIInfo(gfx::mojom::DXGIInfoPtr dxgi_info) {
  // Ignore all DXGI info in headless mode.
}

gfx::NativeWindow ScreenWinHeadless::GetNativeWindowAtScreenPoint(
    const gfx::Point& point,
    const std::set<gfx::NativeWindow>& ignore) const {
  NOTREACHED();
}

gfx::Rect ScreenWinHeadless::GetNativeWindowBoundsInScreen(
    gfx::NativeWindow window) const {
  NOTREACHED();
}

gfx::Rect ScreenWinHeadless::GetHeadlessWindowBounds(
    gfx::AcceleratedWidget window) const {
  NOTREACHED();
}

gfx::NativeWindow ScreenWinHeadless::GetRootWindow(
    gfx::NativeWindow window) const {
  NOTREACHED();
}

ScreenWinDisplay ScreenWinHeadless::GetScreenWinDisplayNearestHWND(
    HWND hwnd) const {
  return GetScreenWinDisplay(
      MonitorInfoFromWindow(hwnd, MONITOR_DEFAULTTONEAREST));
}

ScreenWinDisplay ScreenWinHeadless::GetScreenWinDisplayNearestScreenRect(
    const gfx::Rect& screen_rect) const {
  return GetScreenWinDisplay(MonitorInfoFromScreenRect(screen_rect));
}

ScreenWinDisplay ScreenWinHeadless::GetScreenWinDisplayNearestScreenPoint(
    const gfx::Point& screen_point) const {
  return GetScreenWinDisplay(MonitorInfoFromScreenPoint(screen_point));
}

ScreenWinDisplay ScreenWinHeadless::GetPrimaryScreenWinDisplay() const {
  // ScreenWin::GetPrimaryScreenWinDisplay() searches the ScreenWinDisplay
  // table for a display with origin at (0,0), however, for headless primary
  // display is always the first registered display.
  const int64_t id = GetPrimaryDisplay().id();
  return GetScreenWinDisplayWithDisplayId(id);
}

ScreenWinDisplay ScreenWinHeadless::GetScreenWinDisplay(
    std::optional<MONITORINFOEX> monitor_info) const {
  // ScreenWin::GetScreenWinDisplay() calls
  // DisplayInfo::DisplayIdFromMonitorInfo() to derive display id from monitor
  // info. Headless display ids are synthesized, so retrieve display id from
  // the headless display device name.
  if (monitor_info) {
    const int64_t id = GetHeadlessDisplayIdFromMonitorInfo(*monitor_info);
    return GetScreenWinDisplayWithDisplayId(id);
  }

  return GetPrimaryScreenWinDisplay();
}

ScreenWinDisplay ScreenWinHeadless::GetScreenWinDisplayForHMONITOR(
    HMONITOR monitor) const {
  // Headless displays don't have a real HMONITOR, so all paths that call this
  // method should be overridden.
  NOTREACHED();
}

int64_t ScreenWinHeadless::AddDisplay(const Display& display) {
  int64_t display_id = HeadlessScreenManager::GetNewDisplayId();

  MONITORINFOEX monitor_info = {};
  monitor_info.cbSize = sizeof(monitor_info);
  UpdateMonitorInfoBoundsAndWorkArea(monitor_info, display);
  SetHeadlessDisplayDeviceName(monitor_info, display_id);

  headless_monitor_info_.insert({display_id, monitor_info});

  internal::DisplayInfo display_info(
      display_id, monitor_info, display.device_scale_factor(),
      display.text_scale_multiplier(), display.color_depth(), kSdrWhiteLevel,
      display.rotation(), kDisplayFrequency,
      GetDisplayPhysicalPixelsPerInch(display.device_scale_factor()),
      GetOutputTechnology(display.IsInternal()), display.label());

  // Get the existing display infos and append the new one.
  std::vector<internal::DisplayInfo> display_infos = GetExistingDisplayInfos();
  display_infos.push_back(display_info);

  UpdateFromDisplayInfos(display_infos);

  return display_id;
}

void ScreenWinHeadless::UpdateDisplay(const Display& display) {
  std::vector<Display> displays = GetAllDisplays();
  for (auto& it : displays) {
    if (it.id() == display.id()) {
      it = display;
      UpdateFromDisplays(displays);
      break;
    }
  }
}

void ScreenWinHeadless::RemoveDisplay(int64_t display_id) {
  CHECK_NE(display_id, GetPrimaryDisplay().id());

  if (!headless_monitor_info_.erase(display_id)) {
    return;
  }

  // Get the existing display infos except for the one being removed.
  std::vector<internal::DisplayInfo> display_infos =
      GetExistingDisplayInfos(/*except_display_id=*/display_id);

  UpdateFromDisplayInfos(display_infos);
}

void ScreenWinHeadless::SetPrimaryDisplay(int64_t display_id) {
  DisplayList display_list;
  for (const Display& display : GetAllDisplays()) {
    display_list.AddDisplay(display, display.id() == primary_display_id_
                                         ? DisplayList::Type::PRIMARY
                                         : DisplayList::Type::NOT_PRIMARY);
  }

  headless::SetPrimaryDisplay(display_list, display_id);

  CHECK_EQ(display_list.GetPrimaryDisplayIterator()->id(), display_id);

  primary_display_id_ = display_id;

  UpdateFromDisplays(display_list.displays());
}

std::vector<internal::DisplayInfo>
ScreenWinHeadless::DisplayInfosFromScreenInfo(
    const std::vector<headless::HeadlessScreenInfo>& screen_infos) {
  CHECK(!screen_infos.empty());

  std::optional<float> forced_device_scale_factor;
  if (Display::HasForceDeviceScaleFactor()) {
    forced_device_scale_factor = Display::GetForcedDeviceScaleFactor();
  }

  primary_display_id_ = kInvalidDisplayId;

  bool is_primary = true;
  std::vector<internal::DisplayInfo> display_infos;
  for (const auto& screen_info : screen_infos) {
    int64_t display_id = HeadlessScreenManager::GetNewDisplayId();

    MONITORINFOEX monitor_info;
    monitor_info.cbSize = sizeof(monitor_info);
    monitor_info.rcMonitor = screen_info.bounds.ToRECT();

    if (screen_info.work_area_insets.IsEmpty()) {
      monitor_info.rcWork = monitor_info.rcMonitor;
    } else {
      gfx::Rect work_area = screen_info.bounds;
      work_area.Inset(screen_info.work_area_insets);
      monitor_info.rcWork = work_area.ToRECT();
    }

    monitor_info.dwFlags = is_primary ? MONITORINFOF_PRIMARY : 0;

    SetHeadlessDisplayDeviceName(monitor_info, display_id);

    const float device_scale_factor =
        forced_device_scale_factor.value_or(screen_info.device_pixel_ratio);

    // Maintain display id to monitor info association for all the
    // MonitorInfoFromScreen*() functions below.
    headless_monitor_info_.insert({display_id, monitor_info});

    if (is_primary) {
      primary_display_id_ = display_id;
    }

    internal::DisplayInfo display_info(
        display_id, monitor_info, device_scale_factor, kTextScaleMultiplier,
        screen_info.color_depth, kSdrWhiteLevel,
        Display::DegreesToRotation(screen_info.rotation), kDisplayFrequency,
        GetDisplayPhysicalPixelsPerInch(screen_info.device_pixel_ratio),
        GetOutputTechnology(screen_info.is_internal), screen_info.label);

    display_infos.push_back(std::move(display_info));

    is_primary = false;
  }

  return display_infos;
}

std::vector<internal::DisplayInfo> ScreenWinHeadless::GetExistingDisplayInfos(
    int64_t except_display_id) {
  std::vector<internal::DisplayInfo> display_infos;

  for (const Display& display : GetAllDisplays()) {
    if (display.id() == except_display_id) {
      continue;
    }

    auto monitor_info = GetMONITORINFOFromDisplayId(display.id());
    CHECK(monitor_info);

    internal::DisplayInfo display_info(
        display.id(), *monitor_info, display.device_scale_factor(),
        display.text_scale_multiplier(), display.color_depth(), kSdrWhiteLevel,
        display.rotation(), kDisplayFrequency,
        GetDisplayPhysicalPixelsPerInch(display.device_scale_factor()),
        GetOutputTechnology(display.IsInternal()), display.label());

    display_infos.push_back(std::move(display_info));
  }

  return display_infos;
}

Display ScreenWinHeadless::GetDisplayFromScreenPoint(
    const gfx::Point& point) const {
  if (const Display* display =
          FindDisplayNearestPoint(GetAllDisplays(), point)) {
    return *display;
  }

  return GetPrimaryDisplay();
}

Display ScreenWinHeadless::GetDisplayFromScreenRect(
    const gfx::Rect& rect) const {
  if (const Display* display =
          FindDisplayWithBiggestIntersection(GetAllDisplays(), rect)) {
    return *display;
  }

  if (const Display* display =
          FindDisplayNearestPoint(GetAllDisplays(), rect.CenterPoint())) {
    return *display;
  }

  return GetPrimaryDisplay();
}

std::optional<MONITORINFOEX>
ScreenWinHeadless::GetMONITORINFOFromDisplayIdForTest(int64_t id) const {
  return GetMONITORINFOFromDisplayId(id);
}

std::optional<MONITORINFOEX> ScreenWinHeadless::GetMONITORINFOFromDisplayId(
    int64_t id) const {
  auto it = headless_monitor_info_.find(id);
  if (it == headless_monitor_info_.cend()) {
    return std::nullopt;
  }

  return it->second;
}

void ScreenWinHeadless::UpdateFromDisplays(
    const std::vector<Display>& displays) {
  std::vector<internal::DisplayInfo> display_infos;
  for (const Display& display : displays) {
    CHECK(headless_monitor_info_.find(display.id()) !=
          headless_monitor_info_.cend());
    MONITORINFOEX& monitor_info = headless_monitor_info_[display.id()];
    UpdateMonitorInfoBoundsAndWorkArea(monitor_info, display);
    if (display.id() == primary_display_id_) {
      monitor_info.dwFlags |= MONITORINFOF_PRIMARY;
    } else {
      monitor_info.dwFlags &= ~MONITORINFOF_PRIMARY;
    }

    internal::DisplayInfo display_info(
        display.id(), monitor_info, display.device_scale_factor(),
        display.text_scale_multiplier(), display.color_depth(), kSdrWhiteLevel,
        display.rotation(), kDisplayFrequency,
        GetDisplayPhysicalPixelsPerInch(display.device_scale_factor()),
        GetOutputTechnology(display.IsInternal()), display.label());

    display_infos.push_back(display_info);
  }

  UpdateFromDisplayInfos(display_infos);
}

DISPLAY_EXPORT ScreenWinHeadless* GetScreenWinHeadless() {
  ScreenWin* screen_win = GetScreenWin();
  CHECK(screen_win->IsHeadless());
  return static_cast<ScreenWinHeadless*>(screen_win);
}

namespace internal {
bool VerifyHeadlessDisplayDeviceName(int64_t id,
                                     const MONITORINFOEX& monitor_info) {
  return GetHeadlessDisplayDeviceNameFromDisplayId(id) == monitor_info.szDevice;
}
}  // namespace internal

}  // namespace display::win
