/*
 * Copyright © 2018 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 <williamwold@canonical.com>
 */

#include "helpers.h"
#include "in_process_server.h"

#include <gmock/gmock.h>

#include <vector>
#include <memory>

using namespace testing;

struct TouchTestParams
{
    std::string name;
    std::function<std::unique_ptr<wlcs::Surface>(wlcs::InProcessServer& server, wlcs::Client& client,
                                                 int x, int y,
                                                 int width, int height)> make_surface;
};

std::ostream& operator<<(std::ostream& out, TouchTestParams const& param)
{
    return out << param.name << param.name;
}

class TouchTest:
    public wlcs::InProcessServer,
    public testing::WithParamInterface<TouchTestParams>
{
};

TEST_P(TouchTest, touch_on_surface_seen)
{
    int const window_width = 300, window_height = 300;
    int const window_top_left_x = 64, window_top_left_y = 7;

    wlcs::Client client{the_server()};
    auto const surface = GetParam().make_surface(*this, client,
                                                 window_top_left_x, window_top_left_y,
                                                 window_width, window_height);
    auto const wl_surface = static_cast<struct wl_surface*>(*surface);

    auto touch = the_server().create_touch();
    int touch_x = window_top_left_x + 27;
    int touch_y = window_top_left_y + 8;

    touch.down_at(touch_x, touch_y);
    client.roundtrip();
    ASSERT_THAT(client.touched_window(), Eq(wl_surface)) << "touch did not register on surface";
    ASSERT_THAT(client.touch_position(),
                Eq(std::make_pair(
                    wl_fixed_from_int(touch_x - window_top_left_x),
                    wl_fixed_from_int(touch_y - window_top_left_y)))) << "touch came down in the wrong place";
    touch.up();
    client.roundtrip();
}

TEST_P(TouchTest, touch_and_drag_on_surface_seen)
{
    int const window_width = 300, window_height = 300;
    int const window_top_left_x = 64, window_top_left_y = 12;
    int const touch_x = window_top_left_x + 27, touch_y = window_top_left_y + 140;
    int const dx = 37, dy = -52;

    wlcs::Client client{the_server()};
    auto const surface = GetParam().make_surface(*this, client,
                                                 window_top_left_x, window_top_left_y,
                                                 window_width, window_height);
    auto const wl_surface = static_cast<struct wl_surface*>(*surface);

    auto touch = the_server().create_touch();

    touch.down_at(touch_x, touch_y);
    client.roundtrip();
    ASSERT_THAT(client.touched_window(), Eq(wl_surface)) << "touch did not register on surface";
    ASSERT_THAT(client.touch_position(),
                Eq(std::make_pair(
                    wl_fixed_from_int(touch_x - window_top_left_x),
                    wl_fixed_from_int(touch_y - window_top_left_y)))) << "touch came down in the wrong place";

    touch.move_to(touch_x + dx, touch_y + dy);
    client.roundtrip();
    ASSERT_THAT(client.touched_window(), Eq(wl_surface)) << "surface was unfocused when it shouldn't have been";
    ASSERT_THAT(client.touch_position(),
                Ne(std::make_pair(
                    wl_fixed_from_int(touch_x - window_top_left_x),
                    wl_fixed_from_int(touch_y - window_top_left_y)))) << "touch did not move";
    ASSERT_THAT(client.touch_position(),
                Eq(std::make_pair(
                    wl_fixed_from_int(touch_x - window_top_left_x + dx),
                    wl_fixed_from_int(touch_y - window_top_left_y + dy)))) << "touch did not end up in the right place";

    touch.up();
    client.roundtrip();
}

TEST_P(TouchTest, touch_drag_outside_of_surface_and_back_not_lost)
{
    int const window_width = 300, window_height = 300;
    int const window_top_left_x = 64, window_top_left_y = 12;
    int const touch_a_x = window_top_left_x + 27, touch_a_y = window_top_left_y + 12;
    int const touch_b_x = window_top_left_x - 6, touch_b_y = window_top_left_y + window_width + 8;

    wlcs::Client client{the_server()};
    auto const surface = GetParam().make_surface(*this, client,
                                                 window_top_left_x, window_top_left_y,
                                                 window_width, window_height);
    auto const wl_surface = static_cast<struct wl_surface*>(*surface);

    auto touch = the_server().create_touch();

    touch.down_at(touch_a_x, touch_a_y);
    client.roundtrip();
    ASSERT_THAT(client.touched_window(), Eq(wl_surface)) << "touch did not register on surface";
    ASSERT_THAT(client.touch_position(),
                Eq(std::make_pair(
                    wl_fixed_from_int(touch_a_x - window_top_left_x),
                    wl_fixed_from_int(touch_a_y - window_top_left_y)))) << "touch came down in the wrong place";

    touch.move_to(touch_b_x, touch_b_y);
    client.roundtrip();
    EXPECT_THAT(client.touched_window(), Eq(wl_surface)) << "touch was lost when it moved out of the surface";
    if (client.touched_window() == wl_surface)
    {
        EXPECT_THAT(client.touch_position(),
                    Eq(std::make_pair(
                        wl_fixed_from_int(touch_b_x - window_top_left_x),
                        wl_fixed_from_int(touch_b_y - window_top_left_y)))) << "touch did not end up in the right place outside of the surface";
    }

    touch.move_to(touch_a_x, touch_a_y);
    client.roundtrip();
    EXPECT_THAT(client.touched_window(), Eq(wl_surface)) << "touch did not come back onto surface";
    EXPECT_THAT(client.touch_position(),
                Eq(std::make_pair(
                    wl_fixed_from_int(touch_a_x - window_top_left_x),
                    wl_fixed_from_int(touch_a_y - window_top_left_y)))) << "touch came back in the wrong place";

    touch.up();
    client.roundtrip();
}

INSTANTIATE_TEST_CASE_P(
    WlShellSurface,
    TouchTest,
    testing::Values(
        TouchTestParams{
            "wl_shell_surface",
            [](wlcs::InProcessServer& server, wlcs::Client& client, int x, int y, int width, int height)
                -> std::unique_ptr<wlcs::Surface>
                {
                    auto surface = client.create_wl_shell_surface(
                        width,
                        height);
                    server.the_server().move_surface_to(surface, x, y);
                    return std::make_unique<wlcs::Surface>(std::move(surface));
                }
            }
    ));

INSTANTIATE_TEST_CASE_P(
    XdgShellV6Surface,
    TouchTest,
    testing::Values(
        TouchTestParams{
            "xdg_v6_surface",
            [](wlcs::InProcessServer& server, wlcs::Client& client, int x, int y, int width, int height)
                -> std::unique_ptr<wlcs::Surface>
                {
                    auto surface = client.create_xdg_shell_v6_surface(
                        width,
                        height);
                    server.the_server().move_surface_to(surface, x, y);
                    return std::make_unique<wlcs::Surface>(std::move(surface));;
                }
            }
    ));

INSTANTIATE_TEST_CASE_P(
    WlShellSubsurface,
    TouchTest,
    testing::Values(
        TouchTestParams{
            "wl_shell_subsurface",
            [](wlcs::InProcessServer& server, wlcs::Client& client, int x, int y, int width, int height)
                -> std::unique_ptr<wlcs::Surface>
                {
                    auto main_surface = client.create_wl_shell_surface(
                        width,
                        height);
                    server.the_server().move_surface_to(main_surface, x, y);
                    auto subusrface = wlcs::Subsurface::create_visible(main_surface, 0, 0, width, height);
                    client.run_on_destruction(
                        [main_surface = std::make_shared<wlcs::Surface>(std::move(main_surface))]() mutable
                        {
                            main_surface.reset();
                        });
                    return std::make_unique<wlcs::Surface>(std::move(subusrface));
                }
            }
    ));

INSTANTIATE_TEST_CASE_P(
    XdgShellV6Subsurface,
    TouchTest,
    testing::Values(
        TouchTestParams{
            "xdg_shell_subsurface",
            [](wlcs::InProcessServer& server, wlcs::Client& client, int x, int y, int width, int height)
                -> std::unique_ptr<wlcs::Surface>
                {
                    auto main_surface = client.create_xdg_shell_v6_surface(
                        width,
                        height);
                    server.the_server().move_surface_to(main_surface, x, y);
                    auto subusrface = wlcs::Subsurface::create_visible(main_surface, 0, 0, width, height);
                    client.run_on_destruction(
                        [main_surface = std::make_shared<wlcs::Surface>(std::move(main_surface))]() mutable
                        {
                            main_surface.reset();
                        });
                    return std::make_unique<wlcs::Surface>(std::move(subusrface));
                }
            }
    ));
