// Copyright 2017 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 "base/run_loop.h"
#include "base/strings/utf_string_conversions.h"
#include "services/ui/public/interfaces/window_server_test.mojom.h"
#include "services/ui/public/interfaces/window_tree_constants.mojom.h"
#include "ui/aura/mus/in_flight_change.h"
#include "ui/aura/mus/window_tree_host_mus.h"
#include "ui/aura/test/mus/change_completion_waiter.h"
#include "ui/events/event.h"
#include "ui/events/event_utils.h"
#include "ui/views/mus/mus_client.h"
#include "ui/views/test/widget_test.h"
#include "ui/views/widget/desktop_aura/desktop_native_widget_aura.h"
#include "ui/views/widget/widget.h"

namespace views {
namespace test {
namespace {

// A view used in DragTestInteractive.DragTest as a drag source.
class DraggableView : public views::View {
 public:
  DraggableView() {}
  ~DraggableView() override {}

  // views::View overrides:
  int GetDragOperations(const gfx::Point& press_pt) override {
    return ui::DragDropTypes::DRAG_MOVE;
  }
  void WriteDragData(const gfx::Point& press_pt,
                     OSExchangeData* data) override {
    data->SetString(base::UTF8ToUTF16("test"));
  }

 private:
  DISALLOW_COPY_AND_ASSIGN(DraggableView);
};

// A view used in DragTestInteractive.DragTest as a drop target.
class TargetView : public views::View {
 public:
  TargetView() : dropped_(false) {}
  ~TargetView() override {}

  void WaitForDropped(base::Closure quit_closure) {
    if (dropped_) {
      quit_closure.Run();
      return;
    }

    quit_closure_ = quit_closure;
  }

  // views::View overrides:
  bool GetDropFormats(
      int* formats,
      std::set<ui::Clipboard::FormatType>* format_types) override {
    *formats = ui::OSExchangeData::STRING;
    return true;
  }
  bool AreDropTypesRequired() override { return false; }
  bool CanDrop(const OSExchangeData& data) override { return true; }
  int OnDragUpdated(const ui::DropTargetEvent& event) override {
    return ui::DragDropTypes::DRAG_MOVE;
  }
  int OnPerformDrop(const ui::DropTargetEvent& event) override {
    dropped_ = true;
    if (quit_closure_)
      quit_closure_.Run();
    return ui::DragDropTypes::DRAG_MOVE;
  }

  bool dropped() const { return dropped_; }

 private:
  bool dropped_;

  base::Closure quit_closure_;

  DISALLOW_COPY_AND_ASSIGN(TargetView);
};

std::unique_ptr<ui::PointerEvent> CreateMouseMoveEvent(int x, int y) {
  return std::make_unique<ui::PointerEvent>(ui::MouseEvent(
      ui::ET_MOUSE_MOVED, gfx::Point(x, y), gfx::Point(x, y),
      ui::EventTimeForNow(), ui::EF_LEFT_MOUSE_BUTTON, ui::EF_NONE));
}

std::unique_ptr<ui::PointerEvent> CreateMouseDownEvent(int x, int y) {
  return std::make_unique<ui::PointerEvent>(
      ui::MouseEvent(ui::ET_MOUSE_PRESSED, gfx::Point(x, y), gfx::Point(x, y),
                     ui::EventTimeForNow(), ui::EF_LEFT_MOUSE_BUTTON,
                     ui::EF_LEFT_MOUSE_BUTTON));
}

std::unique_ptr<ui::PointerEvent> CreateMouseUpEvent(int x, int y) {
  return std::make_unique<ui::PointerEvent>(
      ui::MouseEvent(ui::ET_MOUSE_RELEASED, gfx::Point(x, y), gfx::Point(x, y),
                     ui::EventTimeForNow(), ui::EF_LEFT_MOUSE_BUTTON,
                     ui::EF_LEFT_MOUSE_BUTTON));
}

}  // namespace

using DragTestInteractive = WidgetTest;

// Dispatch of events is asynchronous so most of DragTestInteractive.DragTest
// consists of callback functions which will perform an action after the
// previous action has completed.
void DragTest_Part3(int64_t display_id,
                    const base::Closure& quit_closure,
                    bool result) {
  EXPECT_TRUE(result);
  quit_closure.Run();
}

void DragTest_Part2(int64_t display_id,
                    const base::Closure& quit_closure,
                    bool result) {
  EXPECT_TRUE(result);
  if (!result)
    quit_closure.Run();

  ui::mojom::EventInjector* event_injector =
      MusClient::Get()->GetTestingEventInjector();
  event_injector->InjectEvent(
      display_id, CreateMouseUpEvent(30, 30),
      base::BindOnce(&DragTest_Part3, display_id, quit_closure));
}

void DragTest_Part1(int64_t display_id,
                    const base::Closure& quit_closure,
                    bool result) {
  EXPECT_TRUE(result);
  if (!result)
    quit_closure.Run();

  ui::mojom::EventInjector* event_injector =
      MusClient::Get()->GetTestingEventInjector();
  event_injector->InjectEvent(
      display_id, CreateMouseMoveEvent(30, 30),
      base::BindOnce(&DragTest_Part2, display_id, quit_closure));
}

// TODO(http://crbug.com/864616): Hangs indefinitely in mus with ws2.
TEST_F(DragTestInteractive, DISABLED_DragTest) {
  Widget* source_widget = CreateTopLevelFramelessPlatformWidget();
  View* source_view = new DraggableView;
  source_widget->SetContentsView(source_view);
  source_widget->Show();

  aura::test::ChangeCompletionWaiter source_waiter(aura::ChangeType::BOUNDS,
                                                   false);
  source_widget->SetBounds(gfx::Rect(0, 0, 20, 20));
  ASSERT_TRUE(source_waiter.Wait());

  Widget* target_widget = CreateTopLevelFramelessPlatformWidget();
  TargetView* target_view = new TargetView;
  target_widget->SetContentsView(target_view);
  target_widget->Show();

  aura::test::ChangeCompletionWaiter target_waiter(aura::ChangeType::BOUNDS,
                                                   false);
  target_widget->SetBounds(gfx::Rect(20, 20, 20, 20));
  ASSERT_TRUE(target_waiter.Wait());

  auto* dnwa =
      static_cast<DesktopNativeWidgetAura*>(source_widget->native_widget());
  ASSERT_TRUE(dnwa);
  auto* wth = static_cast<aura::WindowTreeHostMus*>(dnwa->host());
  ASSERT_TRUE(wth);
  int64_t display_id = wth->display_id();

  {
    base::RunLoop run_loop;
    ui::mojom::EventInjector* event_injector =
        MusClient::Get()->GetTestingEventInjector();
    event_injector->InjectEvent(
        display_id, CreateMouseDownEvent(10, 10),
        base::BindOnce(&DragTest_Part1, display_id, run_loop.QuitClosure()));

    run_loop.Run();
  }

  // Wait for the event dispatch to cause the final drop signal.
  {
    base::RunLoop run_loop;
    target_view->WaitForDropped(run_loop.QuitClosure());
    run_loop.Run();
  }

  EXPECT_TRUE(target_view->dropped());

  target_widget->Close();
  source_widget->Close();
  RunPendingMessages();
}

}  // namespace test
}  // namespace views
