/*
 * Copyright © 2022 Canonical Ltd.
 *
 * This program is free software: you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 3,
 * as published by the Free Software Foundation.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 * Authored by: William Wold <william.wold@canonical.com>
 */

#include "in_process_server.h"
#include "version_specifier.h"
#include "xdg_output_v1.h"
#include "generated/wlr-virtual-pointer-unstable-v1-client.h"

#include "linux/input.h"
#include <gmock/gmock.h>
#include <thread>

using namespace testing;
using namespace std::chrono_literals;

namespace wlcs
{
WLCS_CREATE_INTERFACE_DESCRIPTOR(zwlr_virtual_pointer_manager_v1)
WLCS_CREATE_INTERFACE_DESCRIPTOR(zwlr_virtual_pointer_v1)
}

namespace
{
class PointerListener
{
public:
    PointerListener(wl_seat* seat);
    ~PointerListener();

    MOCK_METHOD(void, enter, (uint32_t serial, wl_surface* surface, wl_fixed_t surface_x, wl_fixed_t surface_y));
    MOCK_METHOD(void, leave, (uint32_t serial, wl_surface* surface));
    MOCK_METHOD(void, motion, (uint32_t time, wl_fixed_t surface_x, wl_fixed_t surface_y));
    MOCK_METHOD(void, button, (uint32_t serial, uint32_t time, uint32_t button, uint32_t state));
    MOCK_METHOD(void, axis, (uint32_t time, uint32_t axis, wl_fixed_t value));
    MOCK_METHOD(void, frame, ());
    MOCK_METHOD(void, axis_source, (uint32_t axis_source));
    MOCK_METHOD(void, axis_stop, (uint32_t time, uint32_t axis));
    MOCK_METHOD(void, axis_discrete, (uint32_t axis, int32_t discrete));

private:
    wl_pointer* const proxy;
};

PointerListener::PointerListener(wl_seat* seat)
    : proxy{wl_seat_get_pointer(seat)}
{
#define FORWARD_TO_MOCK(method) \
    [](void* data, wl_pointer*, auto... args){ static_cast<PointerListener*>(data)->method(args...); }
    static const wl_pointer_listener listener = {
        FORWARD_TO_MOCK(enter),
        FORWARD_TO_MOCK(leave),
        FORWARD_TO_MOCK(motion),
        FORWARD_TO_MOCK(button),
        FORWARD_TO_MOCK(axis),
        FORWARD_TO_MOCK(frame),
        FORWARD_TO_MOCK(axis_source),
        FORWARD_TO_MOCK(axis_stop),
        FORWARD_TO_MOCK(axis_discrete),
    };
#undef FORWARD_TO_MOCK
    wl_pointer_add_listener(proxy, &listener, this);
}

PointerListener::~PointerListener()
{
    wl_pointer_destroy(proxy);
}

class VirtualPointerV1Test: public wlcs::StartedInProcessServer
{
public:
    VirtualPointerV1Test()
        : receive_client{the_server()},
          send_client{the_server()},
          surface{receive_client.create_visible_surface(surface_width, surface_height)},
          pointer{the_server().create_pointer()},
          listener{receive_client.seat()},
          manager{send_client.bind_if_supported<zwlr_virtual_pointer_manager_v1>(wlcs::AnyVersion)}
    {
        EXPECT_CALL(listener, enter(_, surface.wl_surface(), _, _));
        EXPECT_CALL(listener, motion(_, _, _)).Times(AnyNumber());
        EXPECT_CALL(listener, frame()).Times(AnyNumber());
        the_server().move_surface_to(surface, 0, 0);
        pointer.move_to(pointer_start_x, pointer_start_y);
        send_client.roundtrip();
        receive_client.roundtrip();
        Mock::VerifyAndClearExpectations(&listener);
    }

    wlcs::Client receive_client;
    wlcs::Client send_client;
    wlcs::Surface surface;
    wlcs::Pointer pointer;
    StrictMock<PointerListener> listener;
    wlcs::WlHandle<zwlr_virtual_pointer_manager_v1> manager;

    static int const surface_width = 400;
    static int const surface_height = 400;
    static int const pointer_start_x = 20;
    static int const pointer_start_y = 30;
};
}

TEST_F(VirtualPointerV1Test, when_virtual_pointer_is_moved_client_sees_motion)
{
    int const motion_x = 7;
    int const motion_y = 22;

    EXPECT_CALL(listener, motion(_,
        wl_fixed_from_int(pointer_start_x + motion_x),
        wl_fixed_from_int(pointer_start_y + motion_y)));

    auto recieved_frame = false;
    EXPECT_CALL(listener, frame()).Times(AtLeast(1)).WillOnce(Invoke([&]{ recieved_frame = true; }));

    auto const handle = zwlr_virtual_pointer_manager_v1_create_virtual_pointer(manager, nullptr);
    zwlr_virtual_pointer_v1_motion(handle, 0, wl_fixed_from_int(motion_x), wl_fixed_from_int(motion_y));
    zwlr_virtual_pointer_v1_frame(handle);
    send_client.roundtrip();
    receive_client.dispatch_until([&] { return recieved_frame; });
}

TEST_F(VirtualPointerV1Test, when_virtual_pointer_is_moved_multiple_times_client_sees_motion)
{
    int const motion1_x = 7;
    int const motion1_y = 22;
    int const motion2_x = 5;
    int const motion2_y = -12;

    auto const handle = zwlr_virtual_pointer_manager_v1_create_virtual_pointer(manager, nullptr);

    EXPECT_CALL(listener, motion(_, _, _)).Times(AnyNumber());
    auto recieved_frame = false;
    EXPECT_CALL(listener, frame()).Times(AnyNumber()).WillOnce(Invoke([&]{ recieved_frame = true; }));
    zwlr_virtual_pointer_v1_motion(handle, 0, wl_fixed_from_int(motion1_x), wl_fixed_from_int(motion1_y));
    zwlr_virtual_pointer_v1_frame(handle);
    send_client.roundtrip();
    receive_client.dispatch_until([&] { return recieved_frame; });

    EXPECT_CALL(listener, motion(_,
        wl_fixed_from_int(pointer_start_x + motion1_x + motion2_x),
        wl_fixed_from_int(pointer_start_y + motion1_y + motion2_y)));
    recieved_frame = false;
    EXPECT_CALL(listener, frame()).Times(AtLeast(1)).WillOnce(Invoke([&]{ recieved_frame = true; }));
    zwlr_virtual_pointer_v1_motion(handle, 0, wl_fixed_from_int(motion2_x), wl_fixed_from_int(motion2_y));
    zwlr_virtual_pointer_v1_frame(handle);
    send_client.roundtrip();
    receive_client.dispatch_until([&] { return recieved_frame; });
}

TEST_F(VirtualPointerV1Test, when_virtual_pointer_left_clicks_client_sees_button_down)
{
    EXPECT_CALL(listener, button(_, _, BTN_LEFT, WL_POINTER_BUTTON_STATE_PRESSED));
    auto recieved_frame = false;
    EXPECT_CALL(listener, frame()).Times(AtLeast(1)).WillOnce(Invoke([&]{ recieved_frame = true; }));
    auto const handle = zwlr_virtual_pointer_manager_v1_create_virtual_pointer(manager, nullptr);
    zwlr_virtual_pointer_v1_button(handle, 0, BTN_LEFT, WL_POINTER_BUTTON_STATE_PRESSED);
    zwlr_virtual_pointer_v1_frame(handle);
    send_client.roundtrip();
    receive_client.dispatch_until([&] { return recieved_frame; });
}

TEST_F(VirtualPointerV1Test, when_virtual_pointer_left_releases_client_sees_button_up)
{
    EXPECT_CALL(listener, button(_, _, _, _)).Times(AnyNumber());
    auto recieved_frame = false;
    EXPECT_CALL(listener, frame()).Times(AnyNumber()).WillOnce(Invoke([&]{ recieved_frame = true; }));
    auto const handle = zwlr_virtual_pointer_manager_v1_create_virtual_pointer(manager, nullptr);
    zwlr_virtual_pointer_v1_button(handle, 0, BTN_LEFT, WL_POINTER_BUTTON_STATE_PRESSED);
    zwlr_virtual_pointer_v1_frame(handle);
    send_client.roundtrip();
    receive_client.dispatch_until([&] { return recieved_frame; });
    Mock::VerifyAndClearExpectations(&listener);

    EXPECT_CALL(listener, button(_, _, BTN_LEFT, WL_POINTER_BUTTON_STATE_RELEASED));
    recieved_frame = false;
    EXPECT_CALL(listener, frame()).Times(AtLeast(1)).WillOnce(Invoke([&]{ recieved_frame = true; }));
    zwlr_virtual_pointer_v1_button(handle, 0, BTN_LEFT, WL_POINTER_BUTTON_STATE_RELEASED);
    zwlr_virtual_pointer_v1_frame(handle);
    send_client.roundtrip();
    receive_client.dispatch_until([&] { return recieved_frame; });
}

TEST_F(VirtualPointerV1Test, when_virtual_pointer_given_multiple_button_presses_at_once_client_sees_all)
{
    EXPECT_CALL(listener, button(_, _, BTN_LEFT, WL_POINTER_BUTTON_STATE_PRESSED));
    EXPECT_CALL(listener, button(_, _, BTN_MIDDLE, WL_POINTER_BUTTON_STATE_PRESSED));
    auto recieved_frame = false;
    EXPECT_CALL(listener, frame()).Times(AtLeast(1)).WillOnce(Invoke([&]{ recieved_frame = true; }));
    auto const handle = zwlr_virtual_pointer_manager_v1_create_virtual_pointer(manager, nullptr);
    zwlr_virtual_pointer_v1_button(handle, 0, BTN_LEFT, WL_POINTER_BUTTON_STATE_PRESSED);
    zwlr_virtual_pointer_v1_button(handle, 0, BTN_MIDDLE, WL_POINTER_BUTTON_STATE_PRESSED);
    zwlr_virtual_pointer_v1_frame(handle);
    send_client.roundtrip();
    receive_client.dispatch_until([&] { return recieved_frame; });
}

TEST_F(VirtualPointerV1Test, when_virtual_pointer_presses_and_releases_different_buttons_on_same_frame_client_sees_correct_events)
{
    EXPECT_CALL(listener, button(_, _, _, _)).Times(AnyNumber());
    auto recieved_frame = false;
    EXPECT_CALL(listener, frame()).Times(AnyNumber()).WillRepeatedly(Invoke([&]{ recieved_frame = true; }));
    auto const handle = zwlr_virtual_pointer_manager_v1_create_virtual_pointer(manager, nullptr);
    zwlr_virtual_pointer_v1_button(handle, 0, BTN_LEFT, WL_POINTER_BUTTON_STATE_PRESSED);
    zwlr_virtual_pointer_v1_frame(handle);
    send_client.roundtrip();
    receive_client.dispatch_until([&] { return recieved_frame; });
    Mock::VerifyAndClearExpectations(&listener);

    EXPECT_CALL(listener, button(_, _, BTN_LEFT, WL_POINTER_BUTTON_STATE_RELEASED));
    EXPECT_CALL(listener, button(_, _, BTN_RIGHT, WL_POINTER_BUTTON_STATE_PRESSED));
    recieved_frame = false;
    EXPECT_CALL(listener, frame()).Times(AtLeast(1)).WillOnce(Invoke([&]{ recieved_frame = true; }));
    zwlr_virtual_pointer_v1_button(handle, 0, BTN_LEFT, WL_POINTER_BUTTON_STATE_RELEASED);
    zwlr_virtual_pointer_v1_button(handle, 0, BTN_RIGHT, WL_POINTER_BUTTON_STATE_PRESSED);
    zwlr_virtual_pointer_v1_frame(handle);
    send_client.roundtrip();
    receive_client.dispatch_until([&] { return recieved_frame; });

    // This is a hack around https://github.com/MirServer/mir/issues/2971
    std::this_thread::sleep_for(1ms);
    receive_client.roundtrip();
}

TEST_F(VirtualPointerV1Test, when_virtual_pointer_scrolls_client_sees_axis)
{
    EXPECT_CALL(listener, axis(_, WL_POINTER_AXIS_VERTICAL_SCROLL, wl_fixed_from_int(5)));
    EXPECT_CALL(listener, axis_source(_)).Times(AnyNumber());
    auto recieved_frame = false;
    EXPECT_CALL(listener, frame()).Times(AtLeast(1)).WillOnce(Invoke([&]{ recieved_frame = true; }));
    auto const handle = zwlr_virtual_pointer_manager_v1_create_virtual_pointer(manager, nullptr);
    zwlr_virtual_pointer_v1_axis(handle, 0, WL_POINTER_AXIS_VERTICAL_SCROLL, wl_fixed_from_int(5));
    zwlr_virtual_pointer_v1_frame(handle);
    send_client.roundtrip();
    receive_client.dispatch_until([&] { return recieved_frame; });
}

TEST_F(VirtualPointerV1Test, when_virtual_pointer_scrolls_with_steps_client_sees_axis_descrete)
{
    EXPECT_CALL(listener, axis(_, WL_POINTER_AXIS_HORIZONTAL_SCROLL, wl_fixed_from_int(5)));
    EXPECT_CALL(listener, axis_discrete(WL_POINTER_AXIS_HORIZONTAL_SCROLL, 4));
    EXPECT_CALL(listener, axis_source(_)).Times(AnyNumber());
    auto recieved_frame = false;
    EXPECT_CALL(listener, frame()).Times(AtLeast(1)).WillOnce(Invoke([&]{ recieved_frame = true; }));
    auto const handle = zwlr_virtual_pointer_manager_v1_create_virtual_pointer(manager, nullptr);
    zwlr_virtual_pointer_v1_axis_discrete(handle, 0, WL_POINTER_AXIS_HORIZONTAL_SCROLL, wl_fixed_from_int(5), 4);
    zwlr_virtual_pointer_v1_frame(handle);
    send_client.roundtrip();
    receive_client.dispatch_until([&] { return recieved_frame; });
}

TEST_F(VirtualPointerV1Test, when_virtual_pointer_specifies_axis_source_client_sees_axis_source)
{
    EXPECT_CALL(listener, axis(_, _, _)).Times(AnyNumber());
    EXPECT_CALL(listener, axis_source(WL_POINTER_AXIS_SOURCE_CONTINUOUS));
    auto recieved_frame = false;
    EXPECT_CALL(listener, frame()).Times(AtLeast(1)).WillOnce(Invoke([&]{ recieved_frame = true; }));
    auto const handle = zwlr_virtual_pointer_manager_v1_create_virtual_pointer(manager, nullptr);
    zwlr_virtual_pointer_v1_axis(handle, 0, WL_POINTER_AXIS_VERTICAL_SCROLL, wl_fixed_from_int(5));
    zwlr_virtual_pointer_v1_axis_source(handle, WL_POINTER_AXIS_SOURCE_CONTINUOUS);
    zwlr_virtual_pointer_v1_frame(handle);
    send_client.roundtrip();
    receive_client.dispatch_until([&] { return recieved_frame; });
    auto axis_source = false;
    EXPECT_CALL(listener, axis_source(WL_POINTER_AXIS_SOURCE_WHEEL)).WillOnce(Invoke([&]{ axis_source = true; }));
    zwlr_virtual_pointer_v1_axis(handle, 0, WL_POINTER_AXIS_VERTICAL_SCROLL, wl_fixed_from_int(5));
    zwlr_virtual_pointer_v1_axis_source(handle, WL_POINTER_AXIS_SOURCE_WHEEL);
    zwlr_virtual_pointer_v1_frame(handle);
    send_client.roundtrip();
    receive_client.dispatch_until([&] { return axis_source; });
}

TEST_F(VirtualPointerV1Test, if_frame_is_not_sent_client_sees_no_events)
{
    auto const handle = zwlr_virtual_pointer_manager_v1_create_virtual_pointer(manager, nullptr);
    zwlr_virtual_pointer_v1_motion(handle, 0, wl_fixed_from_int(6), wl_fixed_from_int(7));
    zwlr_virtual_pointer_v1_motion_absolute(handle, 0, 2, 4, 10, 10);
    zwlr_virtual_pointer_v1_button(handle, 0, BTN_LEFT, WL_POINTER_BUTTON_STATE_PRESSED);
    zwlr_virtual_pointer_v1_axis(handle, 0, WL_POINTER_AXIS_VERTICAL_SCROLL, wl_fixed_from_int(5));
    zwlr_virtual_pointer_v1_axis_source(handle, WL_POINTER_AXIS_SOURCE_WHEEL);
    // Should produce no events because there has been no frame
    EXPECT_CALL(listener, motion(_,_,_)).Times(0);
    EXPECT_CALL(listener, button(_,_,_,_)).Times(0);
    EXPECT_CALL(listener, axis(_,_,_)).Times(0);
    EXPECT_CALL(listener, axis_source(_)).Times(0);
    EXPECT_CALL(listener, frame()).Times(0);

    send_client.roundtrip();
    std::this_thread::sleep_for(1ms);
    receive_client.roundtrip();
}

TEST_F(VirtualPointerV1Test, when_virtual_pointer_is_moved_with_absolute_coordinates_with_the_extent_of_the_output_client_sees_motion)
{
    ASSERT_THAT(send_client.output_count(), Ge(1u));
    wlcs::XdgOutputManagerV1 xdg_output_manager{send_client};
    wlcs::XdgOutputV1 xdg_output{xdg_output_manager, 0};
    send_client.roundtrip();
    auto const& output_state = xdg_output.state();
    ASSERT_THAT(output_state.logical_size.operator bool(), Eq(true));
    auto const output_size = output_state.logical_size.value();

    int const move_to_x = 22;
    int const move_to_y = 33;

    EXPECT_CALL(listener, motion(_,
        wl_fixed_from_int(move_to_x),
        wl_fixed_from_int(move_to_y)));
    auto recieved_frame = false;
    EXPECT_CALL(listener, frame()).Times(AtLeast(1)).WillOnce(Invoke([&]{ recieved_frame = true; }));

    auto const handle = zwlr_virtual_pointer_manager_v1_create_virtual_pointer(manager, nullptr);
    zwlr_virtual_pointer_v1_motion_absolute(handle, 0, move_to_x, move_to_y, output_size.first, output_size.second);
    zwlr_virtual_pointer_v1_frame(handle);
    send_client.roundtrip();
    receive_client.dispatch_until([&] { return recieved_frame; });
}

TEST_F(VirtualPointerV1Test, when_virtual_pointer_is_moved_with_absolute_coordinates_with_the_extent_twice_of_the_output_client_sees_motion)
{
    ASSERT_THAT(send_client.output_count(), Ge(1u));
    wlcs::XdgOutputManagerV1 xdg_output_manager{send_client};
    wlcs::XdgOutputV1 xdg_output{xdg_output_manager, 0};
    send_client.roundtrip();
    auto const& output_state = xdg_output.state();
    ASSERT_THAT(output_state.logical_size.operator bool(), Eq(true));
    auto const output_size = output_state.logical_size.value();

    int const move_to_x = 22;
    int const move_to_y = 33;

    EXPECT_CALL(listener, motion(_,
        wl_fixed_from_int(move_to_x),
        wl_fixed_from_int(move_to_y)));
    auto recieved_frame = false;
    EXPECT_CALL(listener, frame()).Times(AtLeast(1)).WillOnce(Invoke([&]{ recieved_frame = true; }));

    auto const handle = zwlr_virtual_pointer_manager_v1_create_virtual_pointer(manager, nullptr);
    zwlr_virtual_pointer_v1_motion_absolute(
        handle, 0,
        move_to_x * 2, move_to_y * 2,
        output_size.first * 2, output_size.second * 2);
    zwlr_virtual_pointer_v1_frame(handle);
    send_client.roundtrip();
    receive_client.dispatch_until([&] { return recieved_frame; });
}
