#include "fl/system/engine_events.h"
#include "fl/stl/int.h"
// Note: fl/stl/cstdio.h intentionally NOT included — workaround for
// zackees/zccache#619 (Windows PCH path-spelling drift). The comment
// above said "for printf debug" but no printf-debug call survived.
#include "fl/stl/noexcept.h"


namespace fl {

// Explicitly define constructor and destructor
EngineEvents::EngineEvents() = default;
EngineEvents::~EngineEvents() FL_NOEXCEPT = default;


EngineEvents::Listener::Listener() {}

EngineEvents::Listener::~Listener() FL_NOEXCEPT {
#if FASTLED_HAS_ENGINE_EVENTS
    EngineEvents *ptr = EngineEvents::getInstance();
    const bool has_listener = ptr && ptr->_hasListener(this);
    if (has_listener) {
        // Warning, the listener should be removed by the subclass. If we are
        // here then the subclass did not remove the listener and we are now in
        // a partial state of destruction and the results may be undefined for
        // multithreaded applications. However, for single threaded (the only
        // option as of 2024, October) this will be ok.
        ptr->_removeListener(this);
    }
#endif
}

#if FASTLED_HAS_ENGINE_EVENTS
EngineEvents *EngineEvents::getInstance() {
    return &Singleton<EngineEvents>::instance();
}
#endif

#if FASTLED_HAS_ENGINE_EVENTS
void EngineEvents::_onPlatformPreLoop() {
    for (auto &item : mListeners) {
        auto listener = item.listener;
        listener->onPlatformPreLoop();
    }
    for (auto &item : mListeners) {
        auto listener = item.listener;
        listener->onPlatformPreLoop2();
    }
}

bool EngineEvents::_hasListener(Listener *listener) {
    auto predicate = [listener](const Pair &pair) {
        return pair.listener == listener;
    };
    return mListeners.find_if(predicate) != mListeners.end();
}

void EngineEvents::_addListener(Listener *listener, int priority) {
    if (_hasListener(listener)) {
        return;
    }
    for (auto it = mListeners.begin(); it != mListeners.end(); ++it) {
        if (it->priority < priority) {
            // this is now the highest priority in this spot.
            EngineEvents::Pair pair = EngineEvents::Pair(listener, priority);
            mListeners.insert(it, pair);
            return;
        }
    }
    EngineEvents::Pair pair = EngineEvents::Pair(listener, priority);
    mListeners.push_back(pair);
}

void EngineEvents::_removeListener(Listener *listener) {
    auto predicate = [listener](const Pair &pair) {
        return pair.listener == listener;
    };
    auto it = mListeners.find_if(predicate);
    if (it != mListeners.end()) {
        mListeners.erase(it);
    }
}

void EngineEvents::_onBeginFrame() {
    // Make the copy of the listener list to avoid issues with listeners being
    // added or removed during the loop.
    ListenerList copy = mListeners;
    for (auto &item : copy) {
        auto listener = item.listener;
        listener->onBeginFrame();
    }
}

void EngineEvents::_onEndShowLeds() {
    // Make the copy of the listener list to avoid issues with listeners being
    // added or removed during the loop.
    ListenerList copy = mListeners;
    for (auto &item : copy) {
        auto listener = item.listener;
        listener->onEndShowLeds();
    }
}

void EngineEvents::_onEndFrame() {
    // Make the copy of the listener list to avoid issues with listeners being
    // added or removed during the loop.
    ListenerList copy = mListeners;
    for (auto &item : copy) {
        auto listener = item.listener;
        listener->onEndFrame();
    }
}

void EngineEvents::_onStripAdded(CLEDController *strip, fl::u32 num_leds) {
    // Make the copy of the listener list to avoid issues with listeners being
    // added or removed during the loop.
    ListenerList copy = mListeners;
    for (auto &item : copy) {
        auto listener = item.listener;
        listener->onStripAdded(strip, num_leds);
    }
}

void EngineEvents::_onCanvasUiSet(CLEDController *strip,
                                  const ScreenMap &screenmap) {
    // Make the copy of the listener list to avoid issues with listeners being
    // added or removed during the loop.
    ListenerList copy = mListeners;
    for (auto &item : copy) {
        auto listener = item.listener;
        listener->onCanvasUiSet(strip, screenmap);
    }
}

void EngineEvents::_onExit() {
    // Make the copy of the listener list to avoid issues with listeners being
    // added or removed during the loop.
    ListenerList copy = mListeners;
    for (auto &item : copy) {
        auto listener = item.listener;
        listener->onExit();
    }
}

#endif // FASTLED_HAS_ENGINE_EVENTS

} // namespace fl
