/*
 * 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: Alan Griffiths <alan@octopull.co.uk>
 */

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

#include <gmock/gmock.h>

using namespace testing;
using namespace wlcs;

namespace
{

struct FrameSubmission : StartedInProcessServer
{
    Client client{the_server()};
    Surface surface{client.create_visible_surface(200, 200)};

    void submit_frame(bool& frame_consumed);

    void wait_for_frame(bool const& frame_consumed);
};

void FrameSubmission::submit_frame(bool& consumed_flag)
{
    static wl_callback_listener const frame_listener
    {
        [](void *data, struct wl_callback* callback, uint32_t /*callback_data*/)
        {
            *static_cast<bool*>(data) = true;
            wl_callback_destroy(callback);
        }
    };

    consumed_flag = false;
    wl_callback_add_listener(wl_surface_frame(surface), &frame_listener, &consumed_flag);
    auto buffer = std::make_shared<wlcs::ShmBuffer>(client, 200, 200);
    wl_surface_attach(surface, *buffer, 0, 0);
    wl_surface_commit(surface);
}

void FrameSubmission::wait_for_frame(bool const& consumed_flag)
{
    // TODO timeout
    client.dispatch_until([&consumed_flag]() { return consumed_flag; });
}
}

TEST_F(FrameSubmission, post_one_frame_at_a_time)
{
    for (auto i = 0; i != 10; ++i)
    {
        auto frame_consumed = false;

        submit_frame(frame_consumed);
        wait_for_frame(frame_consumed);

        EXPECT_THAT(frame_consumed, Eq(true));
    }
}

// Regression test for https://github.com/MirServer/mir/issues/2960
TEST_F(FrameSubmission, test_buffer_can_be_deleted_after_attached)
{
    using namespace testing;

    wlcs::Client client{the_server()};
    auto surface = client.create_visible_surface(200, 200);

    auto buffer = std::make_shared<wlcs::ShmBuffer>(client, 200, 200);
    wl_surface_attach(surface, *buffer, 0, 0);
    buffer.reset();
    /* Check that the destroyed buffer doesn't crash the server
     * It's not clear what the *correct* behaviour is in this case:
     * Weston treats this as committing a null buffer, wlroots appears to
     * treat this as committing the content of the buffer before deletion.
     *
     * *Whatever* the correct behaviour is, "crash" is *definitely*
     * incorrect.
     */
    wl_surface_commit(surface);

    // Roundtrip to ensure the server has processed our weirdness
    client.roundtrip();
}

/* Firefox has a lovely habit of sending an endless stream of
 * wl_surface.frame() requests (maybe it's trying to estimate vsync?!)
 *
 * If a compositor responds immediately to the frame on commit, Firefox
 * ends up looping endlessly. If the compositor *doesn't* respond to the frame
 * request, Firefox decides not to draw anything. 🤷‍♀️
 */
TEST_F(FrameSubmission, when_client_endlessly_requests_frame_then_callbacks_are_throttled)
{
    using namespace testing;
    using namespace std::chrono_literals;

    wlcs::Client annoying_client{the_server()};
    auto surface = client.create_visible_surface(200, 200);

    bool frame_callback_called{false};

    wl_callback_listener const listener = {
        .done = [](void* data, struct wl_callback* callback, [[maybe_unused]]uint32_t timestamp)
        {
            wl_callback_destroy(callback);

            auto callback_called = static_cast<bool*>(data);
            *callback_called = true;
        }
    };

    auto const timeout = std::chrono::steady_clock::now() + 10s;

    do
    {
        frame_callback_called = false;
        wl_callback_add_listener(wl_surface_frame(surface), &listener, &frame_callback_called);
        wl_surface_commit(surface);
        client.roundtrip();
            /* This roundtrip ensures the server has processed everything prior.
             * In particular, if the server sends the frame callback in response to wl_surface.commit, that frame callback
             * will have been processed by now.
             */
    }
    while (frame_callback_called && std::chrono::steady_clock::now() < timeout);

    EXPECT_THAT(std::chrono::steady_clock::now(), Lt(timeout)) << "Timed out looping in frame callback storm";
}
