// Copyright 2016 The Chromium 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 "ui/aura/local/window_port_local.h"

#include "components/viz/service/frame_sinks/frame_sink_manager_impl.h"
#include "ui/aura/client/cursor_client.h"
#include "ui/aura/env.h"
#include "ui/aura/local/layer_tree_frame_sink_local.h"
#include "ui/aura/window.h"
#include "ui/aura/window_delegate.h"
#include "ui/base/layout.h"
#include "ui/display/display.h"
#include "ui/display/screen.h"

namespace aura {
namespace {

class ScopedCursorHider {
 public:
  explicit ScopedCursorHider(Window* window)
      : window_(window), hid_cursor_(false) {
    if (!window_->IsRootWindow())
      return;
    const bool cursor_is_in_bounds = window_->GetBoundsInScreen().Contains(
        Env::GetInstance()->last_mouse_location());
    client::CursorClient* cursor_client = client::GetCursorClient(window_);
    if (cursor_is_in_bounds && cursor_client &&
        cursor_client->IsCursorVisible()) {
      cursor_client->HideCursor();
      hid_cursor_ = true;
    }
  }
  ~ScopedCursorHider() {
    if (!window_->IsRootWindow())
      return;

    // Update the device scale factor of the cursor client only when the last
    // mouse location is on this root window.
    if (hid_cursor_) {
      client::CursorClient* cursor_client = client::GetCursorClient(window_);
      if (cursor_client) {
        const display::Display& display =
            display::Screen::GetScreen()->GetDisplayNearestWindow(window_);
        cursor_client->SetDisplay(display);
        cursor_client->ShowCursor();
      }
    }
  }

 private:
  Window* window_;
  bool hid_cursor_;

  DISALLOW_COPY_AND_ASSIGN(ScopedCursorHider);
};

}  // namespace

WindowPortLocal::WindowPortLocal(Window* window)
    : window_(window), weak_factory_(this) {}

WindowPortLocal::~WindowPortLocal() {}

void WindowPortLocal::OnPreInit(Window* window) {}

void WindowPortLocal::OnDeviceScaleFactorChanged(
    float old_device_scale_factor,
    float new_device_scale_factor) {
  if (last_device_scale_factor_ != new_device_scale_factor &&
      local_surface_id_.is_valid()) {
    last_device_scale_factor_ = new_device_scale_factor;
    local_surface_id_ = parent_local_surface_id_allocator_.GenerateId();
    if (frame_sink_)
      frame_sink_->SetLocalSurfaceId(local_surface_id_);
  }

  ScopedCursorHider hider(window_);
  if (window_->delegate()) {
    window_->delegate()->OnDeviceScaleFactorChanged(old_device_scale_factor,
                                                    new_device_scale_factor);
  }
}

void WindowPortLocal::OnWillAddChild(Window* child) {}

void WindowPortLocal::OnWillRemoveChild(Window* child) {}

void WindowPortLocal::OnWillMoveChild(size_t current_index, size_t dest_index) {
}

void WindowPortLocal::OnVisibilityChanged(bool visible) {}

void WindowPortLocal::OnDidChangeBounds(const gfx::Rect& old_bounds,
                                        const gfx::Rect& new_bounds) {
  if (last_size_ != new_bounds.size() && local_surface_id_.is_valid()) {
    last_size_ = new_bounds.size();
    local_surface_id_ = parent_local_surface_id_allocator_.GenerateId();
    if (frame_sink_)
      frame_sink_->SetLocalSurfaceId(local_surface_id_);
  }
}

void WindowPortLocal::OnDidChangeTransform(
    const gfx::Transform& old_transform,
    const gfx::Transform& new_transform) {}

std::unique_ptr<ui::PropertyData> WindowPortLocal::OnWillChangeProperty(
    const void* key) {
  return nullptr;
}

void WindowPortLocal::OnPropertyChanged(
    const void* key,
    int64_t old_value,
    std::unique_ptr<ui::PropertyData> data) {}

std::unique_ptr<cc::LayerTreeFrameSink>
WindowPortLocal::CreateLayerTreeFrameSink() {
  DCHECK(!frame_sink_id_.is_valid());
  auto* context_factory_private =
      aura::Env::GetInstance()->context_factory_private();
  frame_sink_id_ = context_factory_private->AllocateFrameSinkId();
  auto frame_sink = std::make_unique<LayerTreeFrameSinkLocal>(
      frame_sink_id_, context_factory_private->GetHostFrameSinkManager(),
      window_->GetName());
  frame_sink->SetSurfaceChangedCallback(base::Bind(
      &WindowPortLocal::OnSurfaceChanged, weak_factory_.GetWeakPtr()));
  frame_sink_ = frame_sink->GetWeakPtr();
  AllocateLocalSurfaceId();
  if (window_->GetRootWindow())
    window_->layer()->GetCompositor()->AddFrameSink(frame_sink_id_);
  return std::move(frame_sink);
}

viz::SurfaceId WindowPortLocal::GetSurfaceId() const {
  return viz::SurfaceId(frame_sink_id_, local_surface_id_);
}

void WindowPortLocal::AllocateLocalSurfaceId() {
  last_device_scale_factor_ = ui::GetScaleFactorForNativeView(window_);
  last_size_ = window_->bounds().size();
  local_surface_id_ = parent_local_surface_id_allocator_.GenerateId();
  if (frame_sink_)
    frame_sink_->SetLocalSurfaceId(local_surface_id_);
}

const viz::LocalSurfaceId& WindowPortLocal::GetLocalSurfaceId() {
  if (!local_surface_id_.is_valid())
    AllocateLocalSurfaceId();
  return local_surface_id_;
}

viz::FrameSinkId WindowPortLocal::GetFrameSinkId() const {
  return frame_sink_id_;
}

void WindowPortLocal::OnWindowAddedToRootWindow() {
  if (frame_sink_id_.is_valid())
    window_->layer()->GetCompositor()->AddFrameSink(frame_sink_id_);
}

void WindowPortLocal::OnWillRemoveWindowFromRootWindow() {
  if (frame_sink_id_.is_valid())
    window_->layer()->GetCompositor()->RemoveFrameSink(frame_sink_id_);
}

void WindowPortLocal::OnEventTargetingPolicyChanged() {}

void WindowPortLocal::OnSurfaceChanged(const viz::SurfaceInfo& surface_info) {
  DCHECK_EQ(surface_info.id().frame_sink_id(), frame_sink_id_);
  DCHECK_EQ(surface_info.id().local_surface_id(), local_surface_id_);
  scoped_refptr<viz::SurfaceReferenceFactory> reference_factory =
      aura::Env::GetInstance()
          ->context_factory_private()
          ->GetFrameSinkManager()
          ->surface_manager()
          ->reference_factory();
  window_->layer()->SetShowPrimarySurface(surface_info.id(),
                                          window_->bounds().size(),
                                          SK_ColorWHITE, reference_factory);
  window_->layer()->SetFallbackSurfaceId(surface_info.id());
}

bool WindowPortLocal::ShouldRestackTransientChildren() {
  return true;
}

}  // namespace aura
