// Copyright 2015 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 "ash/aura/wm_window_aura.h"
#include "ash/common/wm/window_positioner.h"
#include "ash/common/wm/window_positioning_utils.h"
#include "ash/common/wm_shell.h"
#include "ash/public/cpp/shell_window_ids.h"
#include "ash/shell.h"
#include "components/exo/buffer.h"
#include "components/exo/shell_surface.h"
#include "components/exo/surface.h"
#include "components/exo/test/exo_test_base.h"
#include "components/exo/test/exo_test_helper.h"
#include "components/exo/touch.h"
#include "components/exo/touch_delegate.h"
#include "components/exo/touch_stylus_delegate.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "ui/events/base_event_utils.h"
#include "ui/events/test/event_generator.h"
#include "ui/views/widget/widget.h"

namespace exo {
namespace {

using TouchTest = test::ExoTestBase;

class MockTouchDelegate : public TouchDelegate {
 public:
  MockTouchDelegate() {}

  // Overridden from TouchDelegate:
  MOCK_METHOD1(OnTouchDestroying, void(Touch*));
  MOCK_CONST_METHOD1(CanAcceptTouchEventsForSurface, bool(Surface*));
  MOCK_METHOD4(OnTouchDown,
               void(Surface*, base::TimeTicks, int, const gfx::PointF&));
  MOCK_METHOD2(OnTouchUp, void(base::TimeTicks, int));
  MOCK_METHOD3(OnTouchMotion, void(base::TimeTicks, int, const gfx::PointF&));
  MOCK_METHOD3(OnTouchShape, void(int, float, float));
  MOCK_METHOD0(OnTouchFrame, void());
  MOCK_METHOD0(OnTouchCancel, void());
};

class MockTouchStylusDelegate : public TouchStylusDelegate {
 public:
  MockTouchStylusDelegate() {}

  // Overridden from TouchStylusDelegate:
  MOCK_METHOD1(OnTouchDestroying, void(Touch*));
  MOCK_METHOD2(OnTouchTool, void(int, ui::EventPointerType));
  MOCK_METHOD3(OnTouchForce, void(base::TimeTicks, int, float));
  MOCK_METHOD3(OnTouchTilt, void(base::TimeTicks, int, const gfx::Vector2dF&));
};

TEST_F(TouchTest, OnTouchDown) {
  MockTouchDelegate delegate;
  std::unique_ptr<Touch> touch(new Touch(&delegate));
  ui::test::EventGenerator generator(ash::Shell::GetPrimaryRootWindow());

  auto bottom_window = exo_test_helper()->CreateWindow(10, 10, false);
  auto top_window = exo_test_helper()->CreateWindow(8, 8, false);

  EXPECT_CALL(delegate, OnTouchShape(testing::_, testing::_, testing::_))
      .Times(testing::AnyNumber());
  EXPECT_CALL(delegate, CanAcceptTouchEventsForSurface(top_window.surface()))
      .WillRepeatedly(testing::Return(true));
  EXPECT_CALL(delegate,
              OnTouchDown(top_window.surface(), testing::_, 1, gfx::PointF()));
  EXPECT_CALL(delegate, OnTouchFrame());
  generator.set_current_location(top_window.origin());
  generator.PressTouchId(1);

  EXPECT_CALL(delegate, CanAcceptTouchEventsForSurface(bottom_window.surface()))
      .WillRepeatedly(testing::Return(true));

  // Second touch point should be relative to the focus surface.
  EXPECT_CALL(delegate, OnTouchDown(top_window.surface(), testing::_, 2,
                                    gfx::PointF(-1, -1)));
  EXPECT_CALL(delegate, OnTouchFrame());

  generator.set_current_location(bottom_window.origin());
  generator.PressTouchId(2);

  EXPECT_CALL(delegate, OnTouchDestroying(touch.get()));
  touch.reset();
}

TEST_F(TouchTest, OnTouchUp) {
  auto window = exo_test_helper()->CreateWindow(10, 10, false);

  MockTouchDelegate delegate;
  std::unique_ptr<Touch> touch(new Touch(&delegate));
  ui::test::EventGenerator generator(ash::Shell::GetPrimaryRootWindow());

  EXPECT_CALL(delegate, OnTouchShape(testing::_, testing::_, testing::_))
      .Times(testing::AnyNumber());
  EXPECT_CALL(delegate, CanAcceptTouchEventsForSurface(window.surface()))
      .WillRepeatedly(testing::Return(true));
  EXPECT_CALL(delegate, OnTouchDown(window.surface(), testing::_, testing::_,
                                    gfx::PointF()))
      .Times(2);
  EXPECT_CALL(delegate, OnTouchFrame()).Times(2);
  generator.set_current_location(window.origin());
  generator.PressTouchId(1);
  generator.PressTouchId(2);

  EXPECT_CALL(delegate, OnTouchUp(testing::_, 1));
  EXPECT_CALL(delegate, OnTouchFrame());
  generator.ReleaseTouchId(1);
  EXPECT_CALL(delegate, OnTouchUp(testing::_, 2));
  EXPECT_CALL(delegate, OnTouchFrame());
  generator.ReleaseTouchId(2);

  EXPECT_CALL(delegate, OnTouchDestroying(touch.get()));
  touch.reset();
}

TEST_F(TouchTest, OnTouchMotion) {
  auto window = exo_test_helper()->CreateWindow(10, 10, false);

  MockTouchDelegate delegate;
  std::unique_ptr<Touch> touch(new Touch(&delegate));
  ui::test::EventGenerator generator(ash::Shell::GetPrimaryRootWindow());

  EXPECT_CALL(delegate, OnTouchShape(testing::_, testing::_, testing::_))
      .Times(testing::AnyNumber());
  EXPECT_CALL(delegate, CanAcceptTouchEventsForSurface(window.surface()))
      .WillRepeatedly(testing::Return(true));
  EXPECT_CALL(delegate, OnTouchDown(window.surface(), testing::_, testing::_,
                                    gfx::PointF()));
  EXPECT_CALL(delegate,
              OnTouchMotion(testing::_, testing::_, gfx::PointF(5, 5)));
  EXPECT_CALL(delegate, OnTouchUp(testing::_, testing::_));
  EXPECT_CALL(delegate, OnTouchFrame()).Times(3);
  generator.set_current_location(window.origin());
  generator.PressMoveAndReleaseTouchBy(5, 5);

  // Check if touch point motion outside focus surface is reported properly to
  // the focus surface.
  EXPECT_CALL(delegate, OnTouchDown(window.surface(), testing::_, testing::_,
                                    gfx::PointF()));
  EXPECT_CALL(delegate,
              OnTouchMotion(testing::_, testing::_, gfx::PointF(100, 100)));
  EXPECT_CALL(delegate, OnTouchUp(testing::_, testing::_));
  EXPECT_CALL(delegate, OnTouchFrame()).Times(3);
  generator.set_current_location(window.origin());
  generator.PressMoveAndReleaseTouchBy(100, 100);

  EXPECT_CALL(delegate, OnTouchDestroying(touch.get()));
  touch.reset();
}

TEST_F(TouchTest, OnTouchShape) {
  auto window = exo_test_helper()->CreateWindow(10, 10, false);

  MockTouchDelegate delegate;
  std::unique_ptr<Touch> touch(new Touch(&delegate));
  ui::test::EventGenerator generator(ash::Shell::GetPrimaryRootWindow());

  EXPECT_CALL(delegate, CanAcceptTouchEventsForSurface(window.surface()))
      .WillRepeatedly(testing::Return(true));
  {
    testing::InSequence sequence;
    EXPECT_CALL(delegate, OnTouchDown(window.surface(), testing::_, testing::_,
                                      gfx::PointF()));
    EXPECT_CALL(delegate, OnTouchShape(testing::_, 1, 1));
    EXPECT_CALL(delegate, OnTouchFrame());
    EXPECT_CALL(delegate,
                OnTouchMotion(testing::_, testing::_, gfx::PointF(5, 5)));
    EXPECT_CALL(delegate, OnTouchShape(testing::_, 1, 1));
    EXPECT_CALL(delegate, OnTouchFrame());
    EXPECT_CALL(delegate,
                OnTouchMotion(testing::_, testing::_, gfx::PointF(10, 10)));
    EXPECT_CALL(delegate, OnTouchShape(testing::_, 20, 10));
    EXPECT_CALL(delegate, OnTouchFrame());
    EXPECT_CALL(delegate, OnTouchUp(testing::_, testing::_));
    EXPECT_CALL(delegate, OnTouchFrame());
  }
  generator.set_current_location(window.origin());
  generator.PressTouch();
  generator.MoveTouchBy(5, 5);
  generator.SetTouchRadius(20, 10);
  generator.MoveTouchBy(5, 5);
  generator.ReleaseTouch();
  EXPECT_CALL(delegate, OnTouchDestroying(touch.get()));
  touch.reset();
}

TEST_F(TouchTest, OnTouchCancel) {
  auto window = exo_test_helper()->CreateWindow(10, 10, false);

  MockTouchDelegate delegate;
  std::unique_ptr<Touch> touch(new Touch(&delegate));
  ui::test::EventGenerator generator(ash::Shell::GetPrimaryRootWindow());

  EXPECT_CALL(delegate, OnTouchShape(testing::_, testing::_, testing::_))
      .Times(testing::AnyNumber());
  EXPECT_CALL(delegate, CanAcceptTouchEventsForSurface(window.surface()))
      .WillRepeatedly(testing::Return(true));
  EXPECT_CALL(delegate, OnTouchDown(window.surface(), testing::_, testing::_,
                                    gfx::PointF()))
      .Times(2);
  EXPECT_CALL(delegate, OnTouchFrame()).Times(2);
  generator.set_current_location(window.origin());
  generator.PressTouchId(1);
  generator.PressTouchId(2);

  // One touch point being canceled is enough for OnTouchCancel to be called.
  EXPECT_CALL(delegate, OnTouchCancel());
  EXPECT_CALL(delegate, OnTouchFrame());
  ui::TouchEvent cancel_event(ui::ET_TOUCH_CANCELLED, gfx::Point(), 1,
                              ui::EventTimeForNow());
  generator.Dispatch(&cancel_event);

  EXPECT_CALL(delegate, OnTouchDestroying(touch.get()));
  touch.reset();
}

TEST_F(TouchTest, IgnoreTouchEventDuringModal) {
  auto window = exo_test_helper()->CreateWindow(10, 10, false);
  auto modal = exo_test_helper()->CreateWindow(5, 5, true);

  MockTouchDelegate delegate;
  std::unique_ptr<Touch> touch(new Touch(&delegate));
  ui::test::EventGenerator generator(ash::Shell::GetPrimaryRootWindow());

  // Make the window modal.
  modal.shell_surface()->SetSystemModal(true);

  EXPECT_TRUE(ash::WmShell::Get()->IsSystemModalWindowOpen());
  EXPECT_CALL(delegate, OnTouchShape(testing::_, testing::_, testing::_))
      .Times(testing::AnyNumber());
  EXPECT_CALL(delegate, CanAcceptTouchEventsForSurface(window.surface()))
      .WillRepeatedly(testing::Return(true));
  EXPECT_CALL(delegate, CanAcceptTouchEventsForSurface(modal.surface()))
      .WillRepeatedly(testing::Return(true));

  // Check if touch events on modal window are registered.
  {
    testing::InSequence sequence;
    EXPECT_CALL(delegate, OnTouchDown(modal.surface(), testing::_, testing::_,
                                      gfx::PointF()));
    EXPECT_CALL(delegate, OnTouchFrame());
    EXPECT_CALL(delegate,
                OnTouchMotion(testing::_, testing::_, gfx::PointF(1, 1)));
    EXPECT_CALL(delegate, OnTouchFrame());
    EXPECT_CALL(delegate, OnTouchUp(testing::_, testing::_));
    EXPECT_CALL(delegate, OnTouchFrame());
  }
  generator.set_current_location(modal.origin());
  generator.PressMoveAndReleaseTouchBy(1, 1);

  // Check if touch events on non-modal window are ignored.
  {
    testing::InSequence sequence;
    EXPECT_CALL(delegate, OnTouchDown(window.surface(), testing::_, testing::_,
                                      gfx::PointF()))
        .Times(0);
    EXPECT_CALL(delegate,
                OnTouchMotion(testing::_, testing::_, gfx::PointF(1, 1)))
        .Times(0);
    EXPECT_CALL(delegate, OnTouchUp(testing::_, testing::_)).Times(0);
    EXPECT_CALL(delegate, OnTouchFrame()).Times(0);
  }
  generator.set_current_location(window.origin());
  generator.PressMoveAndReleaseTouchBy(1, 1);

  // Make the window non-modal.
  modal.shell_surface()->SetSystemModal(false);
  EXPECT_FALSE(ash::WmShell::Get()->IsSystemModalWindowOpen());

  // Check if touch events on non-modal window are registered.
  {
    testing::InSequence sequence;
    EXPECT_CALL(delegate, OnTouchDown(window.surface(), testing::_, testing::_,
                                      gfx::PointF()));
    EXPECT_CALL(delegate, OnTouchFrame());
    EXPECT_CALL(delegate,
                OnTouchMotion(testing::_, testing::_, gfx::PointF(1, 1)));
    EXPECT_CALL(delegate, OnTouchFrame());
    EXPECT_CALL(delegate, OnTouchUp(testing::_, testing::_));
    EXPECT_CALL(delegate, OnTouchFrame());
  }
  generator.set_current_location(window.origin());
  generator.PressMoveAndReleaseTouchBy(1, 1);

  EXPECT_CALL(delegate, OnTouchDestroying(touch.get()));
  touch.reset();
}

TEST_F(TouchTest, OnTouchTool) {
  auto window = exo_test_helper()->CreateWindow(10, 10, false);

  MockTouchDelegate delegate;
  MockTouchStylusDelegate stylus_delegate;
  std::unique_ptr<Touch> touch(new Touch(&delegate));
  ui::test::EventGenerator generator(ash::Shell::GetPrimaryRootWindow());
  touch->SetStylusDelegate(&stylus_delegate);

  EXPECT_CALL(delegate, OnTouchShape(testing::_, testing::_, testing::_))
      .Times(testing::AnyNumber());
  EXPECT_CALL(delegate, CanAcceptTouchEventsForSurface(window.surface()))
      .WillRepeatedly(testing::Return(true));

  // Expect tool change to happen before frame of down event.
  {
    testing::InSequence sequence;
    EXPECT_CALL(delegate, OnTouchDown(window.surface(), testing::_, testing::_,
                                      gfx::PointF()));
    EXPECT_CALL(stylus_delegate,
                OnTouchTool(0, ui::EventPointerType::POINTER_TYPE_PEN));
    EXPECT_CALL(delegate, OnTouchFrame());
    EXPECT_CALL(delegate, OnTouchUp(testing::_, testing::_));
    EXPECT_CALL(delegate, OnTouchFrame());
  }
  generator.set_current_location(window.origin());
  generator.SetTouchPointerType(ui::EventPointerType::POINTER_TYPE_PEN);
  generator.PressTouch();
  generator.ReleaseTouch();

  EXPECT_CALL(delegate, OnTouchDestroying(touch.get()));
  touch.reset();
}

TEST_F(TouchTest, OnTouchForce) {
  auto window = exo_test_helper()->CreateWindow(10, 10, false);

  MockTouchDelegate delegate;
  MockTouchStylusDelegate stylus_delegate;
  std::unique_ptr<Touch> touch(new Touch(&delegate));
  ui::test::EventGenerator generator(ash::Shell::GetPrimaryRootWindow());
  touch->SetStylusDelegate(&stylus_delegate);

  EXPECT_CALL(delegate, OnTouchShape(testing::_, testing::_, testing::_))
      .Times(testing::AnyNumber());
  EXPECT_CALL(delegate, CanAcceptTouchEventsForSurface(window.surface()))
      .WillRepeatedly(testing::Return(true));

  // Expect tool change to happen before frame of down event.
  {
    testing::InSequence sequence;
    EXPECT_CALL(delegate, OnTouchDown(window.surface(), testing::_, testing::_,
                                      gfx::PointF()));
    EXPECT_CALL(stylus_delegate,
                OnTouchTool(0, ui::EventPointerType::POINTER_TYPE_PEN));
    EXPECT_CALL(stylus_delegate, OnTouchForce(testing::_, 0, 1.0));
    EXPECT_CALL(delegate, OnTouchFrame());
    EXPECT_CALL(delegate, OnTouchUp(testing::_, testing::_));
    EXPECT_CALL(delegate, OnTouchFrame());
  }
  generator.set_current_location(window.origin());
  generator.SetTouchPointerType(ui::EventPointerType::POINTER_TYPE_PEN);
  generator.SetTouchForce(1.0);
  generator.PressTouch();
  generator.ReleaseTouch();

  EXPECT_CALL(delegate, OnTouchDestroying(touch.get()));
  touch.reset();
}

TEST_F(TouchTest, OnTouchTilt) {
  auto window = exo_test_helper()->CreateWindow(10, 10, false);

  MockTouchDelegate delegate;
  MockTouchStylusDelegate stylus_delegate;
  std::unique_ptr<Touch> touch(new Touch(&delegate));
  ui::test::EventGenerator generator(ash::Shell::GetPrimaryRootWindow());
  touch->SetStylusDelegate(&stylus_delegate);

  EXPECT_CALL(delegate, OnTouchShape(testing::_, testing::_, testing::_))
      .Times(testing::AnyNumber());
  EXPECT_CALL(delegate, CanAcceptTouchEventsForSurface(window.surface()))
      .WillRepeatedly(testing::Return(true));

  // Expect tool change to happen before frame of down event.
  {
    testing::InSequence sequence;
    EXPECT_CALL(delegate, OnTouchDown(window.surface(), testing::_, testing::_,
                                      gfx::PointF()));
    EXPECT_CALL(stylus_delegate,
                OnTouchTool(0, ui::EventPointerType::POINTER_TYPE_PEN));
    EXPECT_CALL(stylus_delegate,
                OnTouchTilt(testing::_, 0, gfx::Vector2dF(1.0, 2.0)));
    EXPECT_CALL(delegate, OnTouchFrame());
    EXPECT_CALL(delegate, OnTouchUp(testing::_, testing::_));
    EXPECT_CALL(delegate, OnTouchFrame());
  }
  generator.set_current_location(window.origin());
  generator.SetTouchPointerType(ui::EventPointerType::POINTER_TYPE_PEN);
  generator.SetTouchTilt(1.0, 2.0);
  generator.PressTouch();
  generator.ReleaseTouch();

  EXPECT_CALL(delegate, OnTouchDestroying(touch.get()));
  touch.reset();
}

}  // namespace
}  // namespace exo
