#include "tests.hpp"
#include "../../shared.hpp"
#include "../../hyprctlCompat.hpp"
#include <print>
#include <thread>
#include <chrono>
#include <hyprutils/os/Process.hpp>
#include <hyprutils/memory/WeakPtr.hpp>
#include <csignal>
#include <cerrno>
#include "../shared.hpp"

using namespace Hyprutils::OS;
using namespace Hyprutils::Memory;

#define UP CUniquePointer
#define SP CSharedPointer

// TODO: refactor and reuse `Tests::waitUntilWindowsN`
static bool waitForWindowCount(int expectedWindowCnt, std::string_view expectation, int waitMillis = 100, int maxWaitCnt = 50) {
    int counter = 0;
    while (Tests::windowCount() != expectedWindowCnt) {
        counter++;
        std::this_thread::sleep_for(std::chrono::milliseconds(waitMillis));

        if (counter > maxWaitCnt) {
            NLog::log("{}Unmet expectation: {}", Colors::RED, expectation);
            return false;
        }
    }
    return true;
}

// TODO: decompose this into multiple test cases
TEST_CASE(gestures) {
    Tests::spawnKitty();
    ASSERT(Tests::windowCount(), 1);

    // Give the shell a moment to initialize
    std::this_thread::sleep_for(std::chrono::milliseconds(500));

    OK(getFromSocket("/eval hl.plugin.test.gesture('up', 5)"));
    OK(getFromSocket("/eval hl.plugin.test.gesture('down', 5)"));
    OK(getFromSocket("/eval hl.plugin.test.gesture('left', 5)"));
    OK(getFromSocket("/eval hl.plugin.test.gesture('right', 5)"));
    OK(getFromSocket("/eval hl.plugin.test.gesture('right', 4)"));

    EXPECT(waitForWindowCount(0, "Gesture sent paste exit + enter to kitty"), true);

    EXPECT(Tests::windowCount(), 0);

    OK(getFromSocket("/eval hl.plugin.test.gesture('left', 3)"));

    EXPECT(waitForWindowCount(1, "Gesture spawned kitty"), true);

    EXPECT(Tests::windowCount(), 1);

    OK(getFromSocket("/eval hl.plugin.test.gesture('right', 3)"));

    {
        auto str = getFromSocket("/clients");
        EXPECT_CONTAINS(str, "floating: 1");
    }

    OK(getFromSocket("/eval hl.plugin.test.gesture('down', 3)"));

    {
        auto str = getFromSocket("/clients");
        EXPECT_CONTAINS(str, "fullscreen: 2");
    }

    OK(getFromSocket("/eval hl.plugin.test.gesture('down', 3)"));

    {
        auto str = getFromSocket("/clients");
        EXPECT_CONTAINS(str, "fullscreen: 0");
    }

    OK(getFromSocket("/eval hl.plugin.test.alt(1)"));

    OK(getFromSocket("/eval hl.plugin.test.gesture('left', 3)"));

    {
        auto str = getFromSocket("/workspaces");
        EXPECT_CONTAINS(str, "ID 2 (2)");
    }

    OK(getFromSocket("/eval hl.plugin.test.gesture('right', 3)"));

    {
        auto str = getFromSocket("/workspaces");
        EXPECT_NOT_CONTAINS(str, "ID 2 (2)");
    }

    // check for crashes
    OK(getFromSocket("/eval hl.plugin.test.gesture('right', 3)"));

    {
        auto str = getFromSocket("/workspaces");
        EXPECT_NOT_CONTAINS(str, "ID 2 (2)");
    }

    OK(getFromSocket("/eval hl.config({ gestures = { workspace_swipe_invert = 0 } })"));

    OK(getFromSocket("/eval hl.plugin.test.gesture('right', 3)"));

    {
        auto str = getFromSocket("/workspaces");
        EXPECT_CONTAINS(str, "ID 2 (2)");
    }

    OK(getFromSocket("/eval hl.plugin.test.gesture('left', 3)"));

    {
        auto str = getFromSocket("/workspaces");
        EXPECT_NOT_CONTAINS(str, "ID 2 (2)");
    }

    OK(getFromSocket("/eval hl.config({ gestures = { workspace_swipe_invert = 1 } })"));
    OK(getFromSocket("/eval hl.config({ gestures = { workspace_swipe_create_new = 0 } })"));

    OK(getFromSocket("/eval hl.plugin.test.gesture('left', 3)"));

    {
        auto str = getFromSocket("/workspaces");
        EXPECT_NOT_CONTAINS(str, "ID 2 (2)");
        EXPECT_CONTAINS(str, "ID 1 (1)");
    }

    OK(getFromSocket("/eval hl.plugin.test.gesture('down', 3)"));

    {
        auto str = getFromSocket("/clients");
        EXPECT_CONTAINS(str, "floating: 0");
    }

    OK(getFromSocket("/eval hl.plugin.test.alt(0)"));

    OK(getFromSocket("/eval hl.plugin.test.gesture('up', 3)"));

    EXPECT(waitForWindowCount(0, "Gesture closed kitty"), true);

    ASSERT(Tests::windowCount(), 0);

    // This test ensures that `movecursortocorner`, which expects
    // a single-character direction argument, is parsed correctly.
    Tests::spawnKitty();
    OK(getFromSocket("/dispatch hl.dsp.cursor.move_to_corner({ corner = 0, window = 'activewindow' })"));
    const std::string cursorPos1 = getFromSocket("/cursorpos");
    OK(getFromSocket("/eval hl.plugin.test.gesture('left', 4)"));
    const std::string cursorPos2 = getFromSocket("/cursorpos");
    // The cursor should have moved because of the gesture
    EXPECT(cursorPos1 != cursorPos2, true);

    // Test that `workspace previous` works correctly after a workspace gesture.
    {
        OK(getFromSocket("/eval hl.config({ gestures = { workspace_swipe_invert = 0 } })"));
        OK(getFromSocket("/eval hl.config({ gestures = { workspace_swipe_create_new = 1 } })"));
        OK(getFromSocket("/dispatch hl.dsp.focus({ workspace = '3' })"));

        // Come to workspace 5 from workspace 3: 5 will remember that.
        OK(getFromSocket("/dispatch hl.dsp.focus({ workspace = '5' })"));
        Tests::spawnKitty(); // Keep workspace 5 open

        // Swipe from 1 to 5: 5 shall remember that.
        OK(getFromSocket("/dispatch hl.dsp.focus({ workspace = '1' })"));
        OK(getFromSocket("/eval hl.plugin.test.alt(1)"));
        OK(getFromSocket("/eval hl.plugin.test.gesture('right', 3)"));
        OK(getFromSocket("/eval hl.plugin.test.alt(0)"));
        EXPECT_CONTAINS(getFromSocket("/activeworkspace"), "ID 5 (5)");

        // Must return to 1 rather than 3
        OK(getFromSocket("/dispatch hl.dsp.focus({ workspace = 'previous' })"));
        EXPECT_CONTAINS(getFromSocket("/activeworkspace"), "ID 1 (1)");

        OK(getFromSocket("/dispatch hl.dsp.focus({ workspace = 'previous' })"));
        EXPECT_CONTAINS(getFromSocket("/activeworkspace"), "ID 5 (5)");

        OK(getFromSocket("/dispatch hl.dsp.focus({ workspace = '1' })"));
    }
}
