// 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/view_util.h"

#include <utility>

#include "libcef/browser/views/view_adapter.h"

#include "ui/display/display.h"
#include "ui/display/screen.h"
#include "ui/gfx/geometry/point.h"
#include "ui/gfx/geometry/point_conversions.h"
#include "ui/views/widget/widget.h"
#include "ui/views/widget/widget_delegate.h"
#include "ui/views/window/non_client_view.h"

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

#if defined(USE_AURA)
#include "ui/aura/window.h"
#include "ui/views/view_constants_aura.h"
#endif

namespace view_util {

namespace {

// Manages the association between views::View and CefView instances.
class UserData : public base::SupportsUserData::Data {
 public:
  // Create the initial association between the views::View and the CefView. The
  // CefView owns the views::View at this stage.
  static void Register(CefRefPtr<CefView> cef_view) {
    DCHECK(cef_view->IsValid());
    DCHECK(!cef_view->IsAttached());

    views::View* view = CefViewAdapter::GetFor(cef_view)->Get();
    DCHECK(view);

    // The CefView should not already be registered.
    DCHECK(!view->GetUserData(UserDataKey()));

    view->SetUserData(UserDataKey(), base::WrapUnique(new UserData(cef_view)));
  }

  static CefRefPtr<CefView> GetFor(const views::View* view) {
    DCHECK(view);
    UserData* data = static_cast<UserData*>(view->GetUserData(UserDataKey()));
    if (data)
      return data->view_ref_;
    return nullptr;
  }

  // Transfer ownership of the views::View to the caller. The views::View will
  // gain a ref-counted reference to the CefView and the CefView will keep an
  // unowned reference to the views::View. Destruction of the views::View will
  // release the ref-counted reference to the CefView.
  static std::unique_ptr<views::View> PassOwnership(CefRefPtr<CefView> cef_view)
      WARN_UNUSED_RESULT {
    DCHECK(cef_view->IsValid());
    DCHECK(!cef_view->IsAttached());

    std::unique_ptr<views::View> view =
        CefViewAdapter::GetFor(cef_view)->PassOwnership();
    DCHECK(view);

    UserData* data = static_cast<UserData*>(view->GetUserData(UserDataKey()));
    DCHECK(data);
    data->TakeReference();

    return view;
  }

  // The CefView resumes ownership of the views::View. The views::View no longer
  // keeps a ref-counted reference to the CefView.
  static void ResumeOwnership(CefRefPtr<CefView> cef_view) {
    DCHECK(cef_view->IsValid());
    DCHECK(cef_view->IsAttached());

    CefViewAdapter* adapter = CefViewAdapter::GetFor(cef_view);
    adapter->ResumeOwnership();

    views::View* view = adapter->Get();
    DCHECK(view);

    UserData* data = static_cast<UserData*>(view->GetUserData(UserDataKey()));
    DCHECK(data);
    data->ReleaseReference();
  }

 private:
  friend std::default_delete<UserData>;

  explicit UserData(CefRefPtr<CefView> cef_view) : view_ref_(cef_view.get()) {
    DCHECK(view_ref_);
  }

  ~UserData() override {
    if (view_) {
      // The CefView does not own the views::View. Remove the CefView's
      // reference to the views::View.
      CefViewAdapter::GetFor(view_)->Detach();
    }
  }

  void TakeReference() { view_ = view_ref_; }

  void ReleaseReference() { view_ = nullptr; }

  static void* UserDataKey() {
    // We just need a unique constant. Use the address of a static that
    // COMDAT folding won't touch in an optimizing linker.
    static int data_key = 0;
    return reinterpret_cast<void*>(&data_key);
  }

  CefRefPtr<CefView> view_;
  CefView* view_ref_;
};

}  // namespace

const SkColor kDefaultBackgroundColor = SkColorSetARGB(255, 255, 255, 255);
const char kDefaultFontList[] = "Arial, Helvetica, 14px";

void Register(CefRefPtr<CefView> view) {
  UserData::Register(view);
}

CefRefPtr<CefView> GetFor(const views::View* view, bool find_known_parent) {
  if (!view)
    return nullptr;

  if (!find_known_parent)
    return UserData::GetFor(view);

  CefRefPtr<CefView> cef_view;
  const views::View* current_view = view;
  do {
    cef_view = UserData::GetFor(current_view);
    if (cef_view)
      break;
    current_view = current_view->parent();
  } while (current_view);

  return cef_view;
}

views::View* GetFor(CefRefPtr<CefView> view) {
  return CefViewAdapter::GetFor(view)->Get();
}

std::unique_ptr<views::View> PassOwnership(CefRefPtr<CefView> view) {
  return UserData::PassOwnership(view);
}

void ResumeOwnership(CefRefPtr<CefView> view) {
  UserData::ResumeOwnership(view);
}

CefRefPtr<CefWindow> GetWindowFor(views::Widget* widget) {
  CefRefPtr<CefWindow> window;

#if defined(USE_AURA)
  // Retrieve the parent Widget for an overlay.
  if (widget) {
    // See matching logic in CefOverlayViewHost::Init.
    auto widget_view =
        widget->GetNativeView()->GetProperty(views::kHostViewKey);
    if (widget_view) {
      widget = widget_view->GetWidget();
    }
  }
#endif  // defined(USE_AURA)

  if (widget) {
    // The views::WidgetDelegate should be a CefWindowView and |content_view|
    // should be the same CefWindowView. However, just in case the views::Widget
    // was created by something else let's go about this the safer way.
    views::View* content_view = widget->widget_delegate()->GetContentsView();
    CefRefPtr<CefView> cef_view = GetFor(content_view, false);
    if (cef_view && cef_view->AsPanel())
      window = cef_view->AsPanel()->AsWindow();

    // The Window should always exist if we created the views::Widget.
    DCHECK(window);
  }

  return window;
}

display::Display GetDisplayNearestPoint(const gfx::Point& point,
                                        bool input_pixel_coords) {
  gfx::Point find_point = point;
#if defined(OS_WIN)
  if (input_pixel_coords) {
    find_point = gfx::ToFlooredPoint(
        display::win::ScreenWin::ScreenToDIPPoint(gfx::PointF(point)));
  }
#endif
  return display::Screen::GetScreen()->GetDisplayNearestPoint(find_point);
}

display::Display GetDisplayMatchingBounds(const gfx::Rect& bounds,
                                          bool input_pixel_coords) {
  gfx::Rect find_bounds = bounds;
#if defined(OS_WIN)
  if (input_pixel_coords) {
    find_bounds =
        display::win::ScreenWin::ScreenToDIPRect(nullptr, find_bounds);
  }
#endif
  return display::Screen::GetScreen()->GetDisplayMatching(find_bounds);
}

void ConvertPointFromPixels(gfx::Point* point, int device_scale_factor) {
  *point = gfx::ToFlooredPoint(
      gfx::ScalePoint(gfx::PointF(*point), 1.0f / device_scale_factor));
}

void ConvertPointToPixels(gfx::Point* point, int device_scale_factor) {
  *point = gfx::ToFlooredPoint(
      gfx::ScalePoint(gfx::PointF(*point), device_scale_factor));
}

bool ConvertPointToScreen(views::View* view,
                          gfx::Point* point,
                          bool output_pixel_coords) {
  if (!view->GetWidget())
    return false;

  views::View::ConvertPointToScreen(view, point);

  if (output_pixel_coords) {
    const display::Display& display = GetDisplayNearestPoint(*point, false);
    ConvertPointToPixels(point, display.device_scale_factor());
  }

  return true;
}

bool ConvertPointFromScreen(views::View* view,
                            gfx::Point* point,
                            bool input_pixel_coords) {
  if (!view->GetWidget())
    return false;

  if (input_pixel_coords) {
    const display::Display& display = GetDisplayNearestPoint(*point, true);
    ConvertPointFromPixels(point, display.device_scale_factor());
  }

  views::View::ConvertPointFromScreen(view, point);

  return true;
}

bool ConvertPointToWindow(views::View* view, gfx::Point* point) {
  views::Widget* widget = view->GetWidget();
  if (!widget)
    return false;

  views::View::ConvertPointToWidget(view, point);

  if (widget->non_client_view()) {
    views::NonClientFrameView* non_client_frame_view =
        widget->non_client_view()->frame_view();
    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();
      *point -= client_bounds.OffsetFromOrigin();
    }
  }

  return true;
}

bool ConvertPointFromWindow(views::View* view, gfx::Point* point) {
  views::Widget* widget = view->GetWidget();
  if (!widget)
    return false;

  if (widget->non_client_view()) {
    views::NonClientFrameView* non_client_frame_view =
        widget->non_client_view()->frame_view();
    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();
      *point += client_bounds.OffsetFromOrigin();
    }
  }

  views::View::ConvertPointFromWidget(view, point);

  return true;
}

}  // namespace view_util
