/************************************************************************
 *
 * Copyright (C) 2025-2026 IRCAD France
 *
 * This file is part of Sight.
 *
 * Sight is free software: you can redistribute it and/or modify it under
 * the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * Sight 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 Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with Sight. If not, see <https://www.gnu.org/licenses/>.
 *
 ***********************************************************************/

#include <core/runtime/runtime.hpp>

#include <io/joystick/interactor.hpp>

#include <utest/wait.hpp>

#include <doctest/doctest.h>

#include <SDL2/SDL.h>

TEST_SUITE("sight::module::io::joystick")
{
    inline static const std::string MODULE_NAME = "sight::module::io::joystick";

//------------------------------------------------------------------------------

    TEST_CASE("basic")
    {
        namespace joystick = sight::io::joystick;

        // Name of the virtual joystick
        static constexpr auto s_VIRTUAL_NAME = "Virtual Flight Stick";

        struct interactor final : public joystick::interactor
        {
            using joystick::interactor::interactor::devices;

            interactor()
            {
                start_listening_joystick();
            }

            ~interactor() override
            {
                stop_listening_joystick();
            }

            //------------------------------------------------------------------------------

            void joystick_axis_motion_event(const joystick::axis_motion_event& _event) final
            {
                if(_event.device->name == s_VIRTUAL_NAME)
                {
                    received_axis_motion_event++;
                }
            }

            //------------------------------------------------------------------------------

            void joystick_added_event(const joystick::joystick_event& _event) final
            {
                if(_event.device->name == s_VIRTUAL_NAME)
                {
                    received_added_event++;
                }
            }

            //------------------------------------------------------------------------------

            void joystick_removed_event(const joystick::joystick_event& _event) final
            {
                if(_event.device->name == s_VIRTUAL_NAME)
                {
                    received_removed_event++;
                }
            }

            std::atomic_int received_added_event {0};
            std::atomic_int received_removed_event {0};
            std::atomic_int received_axis_motion_event {0};
        };

        struct virtual_device
        {
            virtual_device() :
                index(SDL_JoystickAttachVirtual(SDL_JoystickType::SDL_JOYSTICK_TYPE_FLIGHT_STICK, 1, 1, 1))
            {
                CHECK(index >= 0);
            }

            ~virtual_device()
            {
                if(const auto module = sight::core::runtime::find_module(MODULE_NAME); module && module->is_started())
                {
                    CHECK(SDL_JoystickDetachVirtual(index) >= 0);
                }
            }

            int index {-1};
        };

        {
            interactor interactor_instance;
            virtual_device virtual_device_instance;

            SIGHT_TEST_WAIT(interactor_instance.received_added_event > 0);
            CHECK_EQ(1, interactor_instance.received_added_event);

            // Retrieve the real SDL objects
            const auto virtual_id = SDL_JoystickGetDeviceInstanceID(virtual_device_instance.index);
            CHECK(virtual_id >= 0);

            auto* const virtual_joystick = SDL_JoystickFromInstanceID(virtual_id);
            CHECK(virtual_joystick != nullptr);

            // Send a fake axis motion event
            SDL_JoystickSetVirtualAxis(virtual_joystick, 0, 30000);
            SIGHT_TEST_WAIT(interactor_instance.received_axis_motion_event > 0);
            CHECK_EQ(1, interactor_instance.received_axis_motion_event);

            sight::core::runtime::unload_module(MODULE_NAME);

            // No joystick event should be received after the module is unloaded
            interactor_instance.received_axis_motion_event = 0;
            SDL_JoystickSetVirtualAxis(virtual_joystick, 0, 30000);
            SIGHT_TEST_WAIT(interactor_instance.received_axis_motion_event > 0, 1000);
            CHECK_EQ(0, interactor_instance.received_axis_motion_event);
        }

        {
            // joystick event should be received again once the module is re-loaded, but for NEW interactors
            sight::core::runtime::load_module(MODULE_NAME);

            interactor interactor_instance;

            virtual_device virtual_device_instance;

            SIGHT_TEST_WAIT(interactor_instance.received_added_event > 0);
            CHECK_EQ(1, interactor_instance.received_added_event);

            // Retrieve the real SDL objects
            const auto virtual_id = SDL_JoystickGetDeviceInstanceID(virtual_device_instance.index);
            CHECK(virtual_id >= 0);

            auto* const virtual_joystick = SDL_JoystickFromInstanceID(virtual_id);
            CHECK(virtual_joystick != nullptr);

            SDL_JoystickSetVirtualAxis(virtual_joystick, 0, 30000);
            SIGHT_TEST_WAIT(interactor_instance.received_axis_motion_event > 0);
            CHECK_EQ(1, interactor_instance.received_axis_motion_event);
        }
    }

//------------------------------------------------------------------------------
} // TEST_SUITE
