// Copyright 2018 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/views/mus/remote_view/remote_view_provider.h"

#include <memory>

#include "base/bind.h"
#include "base/macros.h"
#include "base/run_loop.h"
#include "base/unguessable_token.h"
#include "ui/aura/mus/window_mus.h"
#include "ui/aura/test/aura_test_base.h"
#include "ui/aura/test/mus/test_window_tree.h"
#include "ui/aura/window.h"
#include "ui/aura/window_tracker.h"
#include "ui/aura/window_tree_host.h"
#include "ui/gfx/geometry/rect.h"
#include "ui/views/mus/remote_view/remote_view_provider_test_api.h"

namespace views {

class RemoteViewProviderTest : public aura::test::AuraTestBase {
 public:
  RemoteViewProviderTest() = default;
  ~RemoteViewProviderTest() override = default;

  // aura::test::AuraTestBase
  void SetUp() override {
    env_ = aura::Env::CreateInstance();
    EnableMusWithTestWindowTree();
    AuraTestBase::SetUp();

    test::RemoteViewProviderTestApi::SetWindowTreeClient(
        window_tree_client_impl());

    embedded_ = std::make_unique<aura::Window>(nullptr);
    embedded_->set_owned_by_parent(false);
    embedded_->Init(ui::LAYER_NOT_DRAWN);
    embedded_->SetBounds(gfx::Rect(100, 50));

    provider_ = std::make_unique<RemoteViewProvider>(embedded_.get());
  }

  void TearDown() override {
    // EmbedRoot in |provider_| must be released before WindowTreeClient.
    provider_.reset();

    AuraTestBase::TearDown();
  }

  // Gets the embed token and waits for it.
  base::UnguessableToken GetEmbedToken() {
    base::RunLoop run_loop;
    base::UnguessableToken token;
    provider_->GetEmbedToken(base::BindOnce(
        [](base::RunLoop* run_loop, base::UnguessableToken* out_token,
           const base::UnguessableToken& token) {
          *out_token = token;
          run_loop->Quit();
        },
        &run_loop, &token));
    run_loop.Run();
    return token;
  }

  // Simulates EmbedUsingToken call on embedder side and waits for
  // WindowTreeClient to create a local embedder window.
  aura::Window* SimulateEmbedUsingTokenAndGetEmbedder(
      const base::UnguessableToken& token) {
    base::RunLoop run_loop;
    aura::Window* embedder = nullptr;
    provider_->SetCallbacks(
        base::BindRepeating(
            [](base::RunLoop* run_loop, aura::Window** out_embedder,
               aura::Window* embedder) {
              *out_embedder = embedder;
              run_loop->Quit();
            },
            &run_loop, &embedder),
        base::DoNothing() /* OnUnembedCallback */);
    window_tree()->AddEmbedRootForToken(token);
    run_loop.Run();
    return embedder;
  }

  // Helper to simulate embed.
  aura::Window* SimulateEmbed() {
    base::UnguessableToken token = GetEmbedToken();
    if (token.is_empty()) {
      ADD_FAILURE() << "Failed to get embed token.";
      return nullptr;
    }

    return SimulateEmbedUsingTokenAndGetEmbedder(token);
  }

  // Simulates the embedder window close.
  void SimulateEmbedderClose(aura::Window* embedder) {
    base::RunLoop run_loop;
    provider_->SetCallbacks(
        base::DoNothing() /* OnEmbedCallback */,
        base::BindRepeating([](base::RunLoop* run_loop) { run_loop->Quit(); },
                            &run_loop));

    const ws::Id embedder_window_id =
        aura::WindowMus::Get(embedder)->server_id();
    window_tree()->RemoveEmbedderWindow(embedder_window_id);
    run_loop.Run();
  }

 protected:
  std::unique_ptr<aura::Env> env_;
  std::unique_ptr<aura::Window> embedded_;
  std::unique_ptr<RemoteViewProvider> provider_;

 private:
  DISALLOW_COPY_AND_ASSIGN(RemoteViewProviderTest);
};

// Tests the basics on the embedded client.
TEST_F(RemoteViewProviderTest, Embed) {
  aura::Window* embedder = SimulateEmbed();
  ASSERT_TRUE(embedder);

  // |embedded_| has the same non-empty size with |embedder| after embed.
  EXPECT_EQ(embedded_->bounds().size(), embedder->bounds().size());
  EXPECT_FALSE(embedded_->bounds().IsEmpty());
  EXPECT_FALSE(embedder->bounds().IsEmpty());

  // |embedded_| resizes with |embedder|.
  const gfx::Rect new_bounds(embedder->bounds().width() + 100,
                             embedder->bounds().height() + 50);
  embedder->SetBounds(new_bounds);
  EXPECT_EQ(embedded_->bounds().size(), embedder->bounds().size());
  EXPECT_FALSE(embedded_->bounds().IsEmpty());
  EXPECT_FALSE(embedder->bounds().IsEmpty());
}

// Tests when |embedded_| is released first.
TEST_F(RemoteViewProviderTest, EmbeddedReleasedFirst) {
  SimulateEmbed();
  embedded_.reset();
}

// Tests when |provider_| is released first.
TEST_F(RemoteViewProviderTest, ClientReleasedFirst) {
  SimulateEmbed();
  provider_.reset();
}

// Tests when embedder goes away first.
TEST_F(RemoteViewProviderTest, EmbedderReleasedFirst) {
  aura::Window* embedder = SimulateEmbed();
  ASSERT_TRUE(embedder);

  SimulateEmbedderClose(embedder);
}

// Tests that the client can embed again.
TEST_F(RemoteViewProviderTest, EmbedAgain) {
  aura::Window* embedder = SimulateEmbed();
  ASSERT_TRUE(embedder);

  aura::WindowTracker window_tracker;
  window_tracker.Add(embedder);
  SimulateEmbedderClose(embedder);
  // SimulateEmbedderClose() should delete |embedder|.
  EXPECT_TRUE(window_tracker.windows().empty());

  aura::Window* new_embedder = SimulateEmbed();
  // SimulateEmbed() should create a new window.
  ASSERT_TRUE(new_embedder);
}

TEST_F(RemoteViewProviderTest, ScreenBounds) {
  aura::Window* embedder = SimulateEmbed();
  ASSERT_TRUE(embedder);

  const viz::LocalSurfaceId server_changed_local_surface_id(
      1, base::UnguessableToken::Create());
  aura::Window* root_window = embedded_->GetRootWindow();
  ASSERT_TRUE(root_window);
  const gfx::Rect root_bounds(101, 102, 100, 50);
  window_tree_client()->OnWindowBoundsChanged(
      aura::WindowMus::Get(root_window)->server_id(), gfx::Rect(), root_bounds,
      server_changed_local_surface_id);
  EXPECT_EQ(root_bounds, root_window->GetHost()->GetBoundsInPixels());
  EXPECT_EQ(root_bounds.origin(), root_window->GetBoundsInScreen().origin());
}

}  // namespace views
