// Copyright 2016 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 "libcef/browser/views/window_impl.h"

#include "libcef/browser/thread_util.h"
#include "libcef/browser/views/display_impl.h"
#include "libcef/browser/views/fill_layout_impl.h"
#include "libcef/browser/views/layout_util.h"
#include "libcef/browser/views/view_util.h"
#include "libcef/browser/views/window_view.h"

#include "ui/base/test/ui_controls.h"
#include "ui/gfx/geometry/rect.h"
#include "ui/views/controls/menu/menu_runner.h"

#if defined(USE_AURA)
#include "ui/aura/test/ui_controls_factory_aura.h"
#include "ui/base/test/ui_controls_aura.h"
#if defined(OS_LINUX)
#include "ui/views/test/ui_controls_factory_desktop_aurax11.h"
#endif
#endif

#if defined(OS_WIN)
#include "ui/display/win/screen_win.h"
#endif

namespace {

// Based on chrome/test/base/interactive_ui_tests_main.cc.
void InitializeUITesting() {
  static bool initialized = false;
  if (!initialized) {
    ui_controls::EnableUIControls();

#if defined(USE_AURA)
#if defined(OS_LINUX)
    ui_controls::InstallUIControlsAura(
        views::test::CreateUIControlsDesktopAura());
#else
    ui_controls::InstallUIControlsAura(aura::test::CreateUIControlsAura(NULL));
#endif
#endif

    initialized = true;
  }
}

}  // namespace

// static
CefRefPtr<CefWindow> CefWindow::CreateTopLevelWindow(
    CefRefPtr<CefWindowDelegate> delegate) {
  return CefWindowImpl::Create(delegate);
}

// static
CefRefPtr<CefWindowImpl> CefWindowImpl::Create(
    CefRefPtr<CefWindowDelegate> delegate) {
  CEF_REQUIRE_UIT_RETURN(nullptr);
  CefRefPtr<CefWindowImpl> window = new CefWindowImpl(delegate);
  window->Initialize();
  window->CreateWidget();
  if (delegate)
    delegate->OnWindowCreated(window.get());
  return window;
}

void CefWindowImpl::Show() {
  CEF_REQUIRE_VALID_RETURN_VOID();
  if (widget_)
    widget_->Show();
}

void CefWindowImpl::Hide() {
  CEF_REQUIRE_VALID_RETURN_VOID();
  if (widget_)
    widget_->Hide();
}

void CefWindowImpl::CenterWindow(const CefSize& size) {
  CEF_REQUIRE_VALID_RETURN_VOID();
  if (widget_)
    widget_->CenterWindow(gfx::Size(size.width, size.height));
}

void CefWindowImpl::Close() {
  CEF_REQUIRE_VALID_RETURN_VOID();
  if (widget_ && !widget_->IsClosed())
    widget_->Close();
}

bool CefWindowImpl::IsClosed() {
  CEF_REQUIRE_UIT_RETURN(false);
  return destroyed_ || (widget_ && widget_->IsClosed());
}

void CefWindowImpl::Activate() {
  CEF_REQUIRE_VALID_RETURN_VOID();
  if (widget_ && widget_->CanActivate() && !widget_->IsActive())
    widget_->Activate();
}

void CefWindowImpl::Deactivate() {
  CEF_REQUIRE_VALID_RETURN_VOID();
  if (widget_&& widget_->CanActivate() && widget_->IsActive())
    widget_->Deactivate();
}

bool CefWindowImpl::IsActive() {
  CEF_REQUIRE_VALID_RETURN(false);
  if (widget_)
    return widget_->IsActive();
  return false;
}

void CefWindowImpl::BringToTop() {
  CEF_REQUIRE_VALID_RETURN_VOID();
  if (widget_)
    widget_->StackAtTop();
}

void CefWindowImpl::SetAlwaysOnTop(bool on_top) {
  CEF_REQUIRE_VALID_RETURN_VOID();
  if (widget_ && on_top != widget_->IsAlwaysOnTop())
    widget_->SetAlwaysOnTop(on_top);
}

bool CefWindowImpl::IsAlwaysOnTop() {
  CEF_REQUIRE_VALID_RETURN(false);
  if (widget_)
    return widget_->IsAlwaysOnTop();
  return false;
}

void CefWindowImpl::Maximize() {
  CEF_REQUIRE_VALID_RETURN_VOID();
  if (widget_ && !widget_->IsMaximized())
    widget_->Maximize();
}

void CefWindowImpl::Minimize() {
  CEF_REQUIRE_VALID_RETURN_VOID();
  if (widget_ && !widget_->IsMinimized())
    widget_->Minimize();
}

void CefWindowImpl::Restore() {
  CEF_REQUIRE_VALID_RETURN_VOID();
  if (widget_ && (widget_->IsMaximized() || widget_->IsMinimized()))
    widget_->Restore();
}

void CefWindowImpl::SetFullscreen(bool fullscreen) {
  CEF_REQUIRE_VALID_RETURN_VOID();
  if (widget_ && fullscreen != widget_->IsFullscreen())
    widget_->SetFullscreen(fullscreen);
}

bool CefWindowImpl::IsMaximized() {
  CEF_REQUIRE_VALID_RETURN(false);
  if (widget_)
    return widget_->IsMaximized();
  return false;
}

bool CefWindowImpl::IsMinimized() {
  CEF_REQUIRE_VALID_RETURN(false);
  if (widget_)
    return widget_->IsMinimized();
  return false;
}

bool CefWindowImpl::IsFullscreen() {
  CEF_REQUIRE_VALID_RETURN(false);
  if (widget_)
    return widget_->IsFullscreen();
  return false;
}

void CefWindowImpl::SetTitle(const CefString& title) {
  CEF_REQUIRE_VALID_RETURN_VOID();
  if (root_view())
    root_view()->SetTitle(title);
}

CefString CefWindowImpl::GetTitle() {
  CEF_REQUIRE_VALID_RETURN(CefString());
  if (root_view())
    return root_view()->title();
  return CefString();
}

void CefWindowImpl::SetWindowIcon(CefRefPtr<CefImage> image) {
  CEF_REQUIRE_VALID_RETURN_VOID();
  if (root_view())
    root_view()->SetWindowIcon(image);
}

CefRefPtr<CefImage> CefWindowImpl::GetWindowIcon() {
  CEF_REQUIRE_VALID_RETURN(nullptr);
  if (root_view())
    return root_view()->window_icon();
  return nullptr;
}

void CefWindowImpl::SetWindowAppIcon(CefRefPtr<CefImage> image) {
  CEF_REQUIRE_VALID_RETURN_VOID();
  if (root_view())
    root_view()->SetWindowAppIcon(image);
}

CefRefPtr<CefImage> CefWindowImpl::GetWindowAppIcon() {
  CEF_REQUIRE_VALID_RETURN(nullptr);
  if (root_view())
    return root_view()->window_app_icon();
  return nullptr;
}

void CefWindowImpl::GetDebugInfo(base::DictionaryValue* info,
                                 bool include_children) {
  ParentClass::GetDebugInfo(info, include_children);
  if (root_view())
    info->SetString("title", root_view()->title());
}

void CefWindowImpl::ShowMenu(CefRefPtr<CefMenuModel> menu_model,
                             const CefPoint& screen_point,
                             cef_menu_anchor_position_t anchor_position) {
  ShowMenu(nullptr, menu_model, screen_point, anchor_position);
}

void CefWindowImpl::Detach() {
  // OnDeleteDelegate should always be called before Detach().
  DCHECK(!widget_);

  ParentClass::Detach();
}

void CefWindowImpl::SetBounds(const CefRect& bounds) {
  CEF_REQUIRE_VALID_RETURN_VOID();
  if (widget_) {
    widget_->SetBounds(
        gfx::Rect(bounds.x, bounds.y, bounds.width, bounds.height));
  }
}

CefRect CefWindowImpl::GetBounds() {
  CEF_REQUIRE_VALID_RETURN(CefRect());
  gfx::Rect bounds;
  if (widget_)
    bounds = widget_->GetWindowBoundsInScreen();
  return CefRect(bounds.x(), bounds.y(), bounds.width(), bounds.height());
}

CefRect CefWindowImpl::GetBoundsInScreen() {
  return GetBounds();
}

void CefWindowImpl::SetSize(const CefSize& size) {
  CEF_REQUIRE_VALID_RETURN_VOID();
  if (widget_)
    widget_->SetSize(gfx::Size(size.width, size.height));
}

void CefWindowImpl::SetPosition(const CefPoint& position) {
  CEF_REQUIRE_VALID_RETURN_VOID();
  if (widget_) {
    gfx::Rect bounds = widget_->GetWindowBoundsInScreen();
    bounds.set_origin(gfx::Point(position.x, position.y));
    widget_->SetBounds(bounds);
  }
}

void CefWindowImpl::SizeToPreferredSize() {
  CEF_REQUIRE_VALID_RETURN_VOID();
  if (widget_) {
    if (widget_->non_client_view())
      widget_->SetSize(widget_->non_client_view()->GetPreferredSize());
    else
      widget_->SetSize(root_view()->GetPreferredSize());
  }
}

void CefWindowImpl::SetVisible(bool visible) {
  if (visible)
    Show();
  else
    Hide();
}

bool CefWindowImpl::IsVisible() {
  CEF_REQUIRE_VALID_RETURN(false);
  if (widget_)
    return widget_->IsVisible();
  return false;
}

bool CefWindowImpl::IsDrawn() {
  return IsVisible();
}

void CefWindowImpl::SetBackgroundColor(cef_color_t color) {
  CEF_REQUIRE_VALID_RETURN_VOID();
  ParentClass::SetBackgroundColor(color);
  if (widget_ && widget_->GetCompositor())
    widget_->GetCompositor()->SetBackgroundColor(color);
}

bool CefWindowImpl::CanWidgetClose() {
  if (delegate())
    return delegate()->CanClose(this);
  return true;
}

void CefWindowImpl::OnWindowViewDeleted() {
  CancelMenu();

  destroyed_ = true;
  widget_ = nullptr;

  if (delegate())
    delegate()->OnWindowDestroyed(this);

  // Call Detach() here instead of waiting for the root View to be deleted so
  // that any following attempts to call CefWindow methods from the delegate
  // will fail.
  Detach();
}

void CefWindowImpl::MenuClosed(CefRefPtr<CefMenuModelImpl> source) {
  DCHECK_EQ(menu_model_, source);
  menu_model_->RemoveObserver(this);
  menu_model_ = nullptr;
  menu_runner_.reset(nullptr);
}

void CefWindowImpl::ShowMenu(views::MenuButton* menu_button,
                             CefRefPtr<CefMenuModel> menu_model,
                             const CefPoint& screen_point,
                             cef_menu_anchor_position_t anchor_position) {
  CancelMenu();

  if (!widget_)
    return;
  
  CefMenuModelImpl* menu_model_impl =
      static_cast<CefMenuModelImpl*>(menu_model.get());
  if (!menu_model_impl || !menu_model_impl->model())
    return;

  menu_model_ = menu_model_impl;
  menu_model_->AddObserver(this);

  menu_runner_.reset(
      new views::MenuRunner(menu_model_impl->model(),
                            menu_button ? views::MenuRunner::HAS_MNEMONICS :
                                          views::MenuRunner::CONTEXT_MENU));

  views::MenuRunner::RunResult result = menu_runner_->RunMenuAt(
      widget_,
      menu_button,
      gfx::Rect(gfx::Point(screen_point.x, screen_point.y), gfx::Size()),
      static_cast<views::MenuAnchorPosition>(anchor_position),
      ui::MENU_SOURCE_NONE);
  ALLOW_UNUSED_LOCAL(result);
}

void CefWindowImpl::CancelMenu() {
  CEF_REQUIRE_VALID_RETURN_VOID();
  if (menu_runner_)
    menu_runner_->Cancel();
  DCHECK(!menu_model_);
  DCHECK(!menu_runner_);
}

CefRefPtr<CefDisplay> CefWindowImpl::GetDisplay() {
  CEF_REQUIRE_VALID_RETURN(nullptr);
  if (widget_ && root_view()) {
    const display::Display& display = root_view()->GetDisplay();
    if (display.is_valid())
      return new CefDisplayImpl(display);
  }
  return nullptr;
}

CefRect CefWindowImpl::GetClientAreaBoundsInScreen() {
  CEF_REQUIRE_VALID_RETURN(CefRect());
  if (widget_) {
    gfx::Rect bounds = widget_->GetClientAreaBoundsInScreen();

    views::NonClientFrameView* non_client_frame_view =
        root_view()->GetNonClientFrameView();
    if (non_client_frame_view) {
      // When using a custom drawn NonClientFrameView the native Window will not
      // know the actual client bounds. Adjust the native Window bounds for the
      // reported client bounds.
      const gfx::Rect& client_bounds =
          non_client_frame_view->GetBoundsForClientView();
      bounds.set_origin(bounds.origin() + client_bounds.OffsetFromOrigin());
      bounds.set_size(client_bounds.size());
    }

    return CefRect(bounds.x(), bounds.y(), bounds.width(), bounds.height());
  }
  return CefRect();
}

void CefWindowImpl::SetDraggableRegions(
    const std::vector<CefDraggableRegion>& regions) {
  CEF_REQUIRE_VALID_RETURN_VOID();
  if (root_view())
    root_view()->SetDraggableRegions(regions);
}

CefWindowHandle CefWindowImpl::GetWindowHandle() {
  CEF_REQUIRE_VALID_RETURN(kNullWindowHandle);
  return view_util::GetWindowHandle(widget_);
}

void CefWindowImpl::SendKeyPress(int key_code,
                                 uint32 event_flags) {
  CEF_REQUIRE_VALID_RETURN_VOID();
  InitializeUITesting();

  gfx::NativeWindow native_window = view_util::GetNativeWindow(widget_);
  if (!native_window)
    return;

  ui_controls::SendKeyPress(native_window,
                            static_cast<ui::KeyboardCode>(key_code),
                            !!(event_flags & EVENTFLAG_CONTROL_DOWN),
                            !!(event_flags & EVENTFLAG_SHIFT_DOWN),
                            !!(event_flags & EVENTFLAG_ALT_DOWN),
                            false);  // Command key is not supported by Aura.
}

void CefWindowImpl::SendMouseMove(int screen_x, int screen_y) {
  CEF_REQUIRE_VALID_RETURN_VOID();
  InitializeUITesting();

  gfx::Point point(screen_x, screen_y);
#if defined(OS_WIN)
  // Windows expects pixel coordinates.
  point = display::win::ScreenWin::DIPToScreenPoint(point);
#endif

  ui_controls::SendMouseMove(point.x(), point.y());
}

void CefWindowImpl::SendMouseEvents(cef_mouse_button_type_t button,
                                    bool mouse_down,
                                    bool mouse_up) {
  CEF_REQUIRE_VALID_RETURN_VOID();
  if (!mouse_down && !mouse_up)
    return;

  InitializeUITesting();

  ui_controls::MouseButton type = ui_controls::LEFT;
  if (button == MBT_MIDDLE)
    type = ui_controls::MIDDLE;
  else if (button == MBT_RIGHT)
    type = ui_controls::RIGHT;

  int state = 0;
  if (mouse_down)
    state |= ui_controls::DOWN;
  if (mouse_up)
    state |= ui_controls::UP;

  ui_controls::SendMouseEvents(type, state);
}

CefWindowImpl::CefWindowImpl(CefRefPtr<CefWindowDelegate> delegate)
    : ParentClass(delegate),
      widget_(nullptr),
      destroyed_(false) {
}

CefWindowView* CefWindowImpl::CreateRootView() {
  return new CefWindowView(delegate(), this);
}

void CefWindowImpl::InitializeRootView() {
  static_cast<CefWindowView*>(root_view())->Initialize();
}

void CefWindowImpl::CreateWidget() {
  DCHECK(!widget_);

  root_view()->CreateWidget();
  widget_ = root_view()->GetWidget();
  DCHECK(widget_);

  // The Widget and root View are owned by the native window. Therefore don't
  // keep an owned reference.
  std::unique_ptr<views::View> view_ptr = view_util::PassOwnership(this);
  views::View* view = view_ptr.release();
  ALLOW_UNUSED_LOCAL(view);
}
