// Copyright (c) 2021 The Chromium Embedded Framework Authors. All rights
// reserved. Use of this source code is governed by a BSD-style license that
// can be found in the LICENSE file.

#include "tests/cefclient/browser/views_overlay_controls.h"

#include <algorithm>
#include <array>
#include <string>

#include "include/views/cef_box_layout.h"
#include "include/views/cef_window.h"
#include "tests/cefclient/browser/views_style.h"

namespace client {

namespace {

constexpr int kInsets = 4;
constexpr int kLocationBarPadding = 100;

// White with 80% opacity.
constexpr auto kBackgroundColor = CefColorSetARGB(255 * .80, 255, 255, 255);

std::string GetLabel(ViewsOverlayControls::Command command, bool maximized) {
  switch (command) {
    case ViewsOverlayControls::Command::kMinimize:
      return "-";
    case ViewsOverlayControls::Command::kMaximize:
      return maximized ? "O" : "o";
    case ViewsOverlayControls::Command::kClose:
      return "X";
  }
  NOTREACHED();
  return std::string();
}

std::array<ViewsOverlayControls::Command, 3> GetButtons() {
#if defined(OS_MAC)
  return {ViewsOverlayControls::Command::kClose,
          ViewsOverlayControls::Command::kMaximize,
          ViewsOverlayControls::Command::kMinimize};
#else
  return {ViewsOverlayControls::Command::kMinimize,
          ViewsOverlayControls::Command::kMaximize,
          ViewsOverlayControls::Command::kClose};
#endif
}

cef_docking_mode_t GetPanelDockingMode(bool use_bottom_controls) {
#if defined(OS_MAC)
  return use_bottom_controls ? CEF_DOCKING_MODE_BOTTOM_LEFT
                             : CEF_DOCKING_MODE_TOP_LEFT;
#else
  return use_bottom_controls ? CEF_DOCKING_MODE_BOTTOM_RIGHT
                             : CEF_DOCKING_MODE_TOP_RIGHT;
#endif
}
cef_docking_mode_t GetMenuDockingMode(bool use_bottom_controls) {
#if defined(OS_MAC)
  return use_bottom_controls ? CEF_DOCKING_MODE_BOTTOM_RIGHT
                             : CEF_DOCKING_MODE_TOP_RIGHT;
#else
  return use_bottom_controls ? CEF_DOCKING_MODE_BOTTOM_LEFT
                             : CEF_DOCKING_MODE_TOP_LEFT;
#endif
}

}  // namespace

ViewsOverlayControls::ViewsOverlayControls(bool with_window_buttons,
                                           bool use_bottom_controls)
    : with_window_buttons_(with_window_buttons),
      use_bottom_controls_(use_bottom_controls) {}

void ViewsOverlayControls::Initialize(CefRefPtr<CefWindow> window,
                                      CefRefPtr<CefMenuButton> menu_button,
                                      CefRefPtr<CefView> location_bar,
                                      bool is_chrome_toolbar) {
  DCHECK(!window_);
  DCHECK(menu_button);
  DCHECK(location_bar);

  window_ = window;
  window_maximized_ = window_->IsMaximized();

  CefInsets insets;
  if (use_bottom_controls_) {
    insets.Set(0, kInsets, kInsets, kInsets);
  } else {
    insets.Set(kInsets, kInsets, 0, kInsets);
  }

  if (with_window_buttons_) {
    // Window control buttons. These controls are currently text which means
    // that we can't use a transparent background because subpixel text
    // rendering will break.
    // See comments on the related DCHECK in Label::PaintText.
    panel_ = CefPanel::CreatePanel(this);

    // Use a horizontal box layout.
    CefBoxLayoutSettings panel_layout_settings;
    panel_layout_settings.horizontal = true;
    panel_->SetToBoxLayout(panel_layout_settings);

    for (auto button : GetButtons()) {
      panel_->AddChildView(CreateButton(button));
    }
    panel_controller_ = window->AddOverlayView(
        panel_, GetPanelDockingMode(use_bottom_controls_),
        /*can_activate=*/false);
    panel_controller_->SetInsets(insets);
    panel_controller_->SetVisible(true);
  }

  // Menu button.
  menu_button->SetBackgroundColor(kBackgroundColor);
  menu_controller_ = window_->AddOverlayView(
      menu_button, GetMenuDockingMode(use_bottom_controls_),
      /*can_activate=*/false);
  menu_controller_->SetInsets(insets);
  menu_controller_->SetVisible(true);

  // Location bar. Will be made visible in UpdateControls().
  location_bar_ = location_bar;
  is_chrome_toolbar_ = is_chrome_toolbar;
  // Use a 100% transparent background for the Chrome toolbar.
  location_bar_->SetBackgroundColor(is_chrome_toolbar_ ? 0 : kBackgroundColor);
  location_controller_ = window_->AddOverlayView(
      location_bar_, CEF_DOCKING_MODE_CUSTOM, /*can_activate=*/false);
}

void ViewsOverlayControls::Destroy() {
  window_ = nullptr;
  panel_ = nullptr;
  if (panel_controller_) {
    panel_controller_->Destroy();
    panel_controller_ = nullptr;
  }
  menu_controller_->Destroy();
  menu_controller_ = nullptr;
  location_bar_ = nullptr;
  location_controller_->Destroy();
  location_controller_ = nullptr;
}

void ViewsOverlayControls::UpdateControls() {
  // Update location bar size, position and visibility.
  const auto window_bounds = window_->GetBounds();
  auto bounds = window_bounds;
  bounds.x = kLocationBarPadding;
  bounds.width -= kLocationBarPadding * 2;

  if (is_chrome_toolbar_) {
    // Fit the standard Chrome toolbar.
    const auto preferred_size = location_bar_->GetPreferredSize();
    bounds.height =
        std::max(menu_controller_->GetSize().height, preferred_size.height);
  } else {
    bounds.height = menu_controller_->GetSize().height;
  }

  if (use_bottom_controls_) {
    bounds.y = window_bounds.height - bounds.height - kInsets;
  } else {
    bounds.y = kInsets;
  }

  if (bounds.width < kLocationBarPadding * 2) {
    // Not enough space.
    location_controller_->SetVisible(false);
  } else {
    location_bar_->SetSize(CefSize(bounds.width, bounds.height));
    location_controller_->SetBounds(bounds);
    location_controller_->SetVisible(true);
  }

  MaybeUpdateMaximizeButton();
}

void ViewsOverlayControls::UpdateDraggableRegions(
    std::vector<CefDraggableRegion>& window_regions) {
  if (panel_controller_ && panel_controller_->IsVisible()) {
    window_regions.emplace_back(panel_controller_->GetBounds(),
                                /*draggable=*/false);
  }

  if (menu_controller_ && menu_controller_->IsVisible()) {
    window_regions.emplace_back(menu_controller_->GetBounds(),
                                /*draggable=*/false);
  }

  if (location_controller_ && location_controller_->IsVisible()) {
    window_regions.emplace_back(location_controller_->GetBounds(),
                                /*draggable=*/false);
  }
}

void ViewsOverlayControls::OnButtonPressed(CefRefPtr<CefButton> button) {
  auto command = static_cast<Command>(button->GetID());
  switch (command) {
    case ViewsOverlayControls::Command::kMinimize:
      window_->Minimize();
      break;
    case ViewsOverlayControls::Command::kMaximize:
      if (window_->IsMaximized()) {
        window_->Restore();
      } else {
        window_->Maximize();
      }
      break;
    case ViewsOverlayControls::Command::kClose:
      window_->Close();
      return;
  }

  // Explicitly reset button state because the button may have moved and it
  // won't receive the corresponding mouse move events.
  button->SetState(CEF_BUTTON_STATE_NORMAL);
  button->SetInkDropEnabled(false);
  button->SetInkDropEnabled(true);

  if (command == Command::kMaximize) {
    MaybeUpdateMaximizeButton();
  }
}

void ViewsOverlayControls::OnThemeChanged(CefRefPtr<CefView> view) {
  // Apply colors when the theme changes.
  views_style::OnThemeChanged(view);
}

CefRefPtr<CefLabelButton> ViewsOverlayControls::CreateButton(Command command) {
  CefRefPtr<CefLabelButton> button = CefLabelButton::CreateLabelButton(
      this, GetLabel(command, window_maximized_));
  button->SetID(static_cast<int>(command));
  button->SetInkDropEnabled(true);
  button->SetFocusable(false);  // Don't give focus to the button.
  return button;
}

void ViewsOverlayControls::MaybeUpdateMaximizeButton() {
  if (!with_window_buttons_ || window_->IsMaximized() == window_maximized_) {
    return;
  }
  window_maximized_ = !window_maximized_;

  auto max_button = panel_->GetChildViewAt(1);
  auto command = static_cast<Command>(max_button->GetID());
  DCHECK(command == Command::kMaximize);
  max_button->AsButton()->AsLabelButton()->SetText(
      GetLabel(command, window_maximized_));

  // Adjust overlay size and placement due to layout changing.
  panel_controller_->SizeToPreferredSize();
}

}  // namespace client