// Copyright (c) 2022, NVIDIA CORPORATION. All rights reserved.
//
// NVIDIA CORPORATION and its licensors retain all intellectual property
// and proprietary rights in and to this software, related documentation
// and any modifications thereto.  Any use, reproduction, disclosure or
// distribution of this software and related documentation without an express
// license agreement from NVIDIA CORPORATION is strictly prohibited.
//

#include <OgnReadViewportHoverStateDatabase.h>

#include "ViewportHoverNodeCommon.h"

#include <omni/kit/IApp.h>
#include <omni/ui/Workspace.h>

namespace omni
{
namespace graph
{
namespace ui
{
class OgnReadViewportHoverState
{
public:
    struct InternalState
    {
        carb::events::ISubscriptionPtr hoverBeganSub;
        carb::events::ISubscriptionPtr hoverChangedSub;
        carb::events::ISubscriptionPtr hoverEndedSub;
        carb::events::ISubscriptionPtr updateSub;
        ViewportHoverEventPayloads eventPayloads;
        ViewportHoverEventStates eventStates;
    } m_internalState;

    // Process event payloads and update event states every frame
    static void updateEventStates(ViewportHoverEventStates& eventStates, ViewportHoverEventPayloads& eventPayloads)
    {
        for (auto& eventState : eventStates)
        {
            eventState.second.velocityNorm = {0.0, 0.0};
            eventState.second.velocityPixel = {0.0, 0.0};
        }

        for (auto const& hoverBeganPayload : eventPayloads.hoverBeganPayloads())
        {
            if (!hoverBeganPayload.second.isValid)
                continue;

            auto& eventStateValue = eventStates[hoverBeganPayload.first];
            eventStateValue.isHovered = true;

            eventStateValue.positionNorm = hoverBeganPayload.second.positionNorm;
            eventStateValue.positionPixel = hoverBeganPayload.second.positionPixel;
            eventStateValue.velocityNorm = {0.0, 0.0};
            eventStateValue.velocityPixel = {0.0, 0.0};
        }

        for (auto const& hoverChangedPayload : eventPayloads.hoverChangedPayloads())
        {
            if (!hoverChangedPayload.second.isValid)
                continue;

            auto& eventStateValue = eventStates[hoverChangedPayload.first];
            if (eventStateValue.isHovered)
            {
                eventStateValue.positionNorm = hoverChangedPayload.second.positionNorm;
                eventStateValue.positionPixel = hoverChangedPayload.second.positionPixel;
                eventStateValue.velocityNorm = hoverChangedPayload.second.velocityNorm;
                eventStateValue.velocityPixel = hoverChangedPayload.second.velocityPixel;
            }
        }

        for (auto const& hoverEndedPayload : eventPayloads.hoverEndedPayloads())
        {
            if (!hoverEndedPayload.second.isValid)
                continue;

            auto& eventStateValue = eventStates[hoverEndedPayload.first];
            if (eventStateValue.isHovered)
            {
                eventStateValue.isHovered = false;
                
                eventStateValue.positionNorm = {0.0, 0.0};
                eventStateValue.positionPixel = {0.0, 0.0};
                eventStateValue.velocityNorm = {0.0, 0.0};
                eventStateValue.velocityPixel = {0.0, 0.0};
            }
        }

        eventPayloads.clear();
    }

    static void initialize(GraphContextObj const&, NodeObj const& nodeObj)
    {
        OgnReadViewportHoverState& state = OgnReadViewportHoverStateDatabase::sm_stateManagerOgnReadViewportHoverState
            .getState<OgnReadViewportHoverState>(nodeObj.nodeHandle);

        // Subscribe to hover events and update tick
        if (omni::kit::IApp* app = carb::getCachedInterface<omni::kit::IApp>())
        {
            state.m_internalState.hoverBeganSub = carb::events::createSubscriptionToPushByType(
                app->getMessageBusEventStream(),
                kHoverBeganEventType,
                [nodeObj](carb::events::IEvent* e)
                {
                    if (e)
                    {
                        OgnReadViewportHoverState& state = OgnReadViewportHoverStateDatabase::sm_stateManagerOgnReadViewportHoverState
                            .getState<OgnReadViewportHoverState>(nodeObj.nodeHandle);
                        state.m_internalState.eventPayloads.setHoverBeganPayload(e->payload);
                    }
                }
            );
            state.m_internalState.hoverChangedSub = carb::events::createSubscriptionToPushByType(
                app->getMessageBusEventStream(),
                kHoverChangedEventType,
                [nodeObj](carb::events::IEvent* e)
                {
                    if (e)
                    {
                        OgnReadViewportHoverState& state = OgnReadViewportHoverStateDatabase::sm_stateManagerOgnReadViewportHoverState
                            .getState<OgnReadViewportHoverState>(nodeObj.nodeHandle);
                        state.m_internalState.eventPayloads.setHoverChangedPayload(e->payload);
                    }
                }
            );
            state.m_internalState.hoverEndedSub = carb::events::createSubscriptionToPushByType(
                app->getMessageBusEventStream(),
                kHoverEndedEventType,
                [nodeObj](carb::events::IEvent* e)
                {
                    if (e)
                    {
                        OgnReadViewportHoverState& state = OgnReadViewportHoverStateDatabase::sm_stateManagerOgnReadViewportHoverState
                            .getState<OgnReadViewportHoverState>(nodeObj.nodeHandle);
                        state.m_internalState.eventPayloads.setHoverEndedPayload(e->payload);
                    }
                }
            );
            state.m_internalState.updateSub = carb::events::createSubscriptionToPush(
                app->getUpdateEventStream(),
                [nodeObj](carb::events::IEvent* e)
                {
                    if (e)
                    {
                        OgnReadViewportHoverState& state = OgnReadViewportHoverStateDatabase::sm_stateManagerOgnReadViewportHoverState
                            .getState<OgnReadViewportHoverState>(nodeObj.nodeHandle);
                        updateEventStates(state.m_internalState.eventStates, state.m_internalState.eventPayloads);
                    }
                }
            );
        }
    }

    static void release(const NodeObj& nodeObj)
    {
        OgnReadViewportHoverState& state = OgnReadViewportHoverStateDatabase::sm_stateManagerOgnReadViewportHoverState
            .getState<OgnReadViewportHoverState>(nodeObj.nodeHandle);

        // Unsubscribe from hover events and update tick
        if (state.m_internalState.hoverBeganSub.get())
            state.m_internalState.hoverBeganSub.detach()->unsubscribe();

        if (state.m_internalState.hoverChangedSub.get())
            state.m_internalState.hoverChangedSub.detach()->unsubscribe();

        if (state.m_internalState.hoverEndedSub.get())
            state.m_internalState.hoverEndedSub.detach()->unsubscribe();

        if (state.m_internalState.updateSub.get())
            state.m_internalState.updateSub.detach()->unsubscribe();
    }

    static bool compute(OgnReadViewportHoverStateDatabase& db)
    {
        OgnReadViewportHoverState& state = db.internalState<OgnReadViewportHoverState>();

        // Get the targeted viewport
        char const* const viewportWindowName = db.tokenToString(db.inputs.viewport());
        if (!omni::ui::Workspace::getWindow(viewportWindowName))
        {
            db.logWarning("Viewport window '%s' not found", viewportWindowName);
        }

        // Output hover state
        auto it = state.m_internalState.eventStates.find({viewportWindowName});
        if (it != state.m_internalState.eventStates.end())
        {
            if (db.inputs.useNormalizedCoords())
            {
                db.outputs.position() = it->second.positionNorm;
                db.outputs.velocity() = it->second.velocityNorm;
            }
            else
            {
                db.outputs.position() = it->second.positionPixel;
                db.outputs.velocity() = it->second.velocityPixel;
            }
            db.outputs.isHovered() = it->second.isHovered;
            db.outputs.isValid() = true;
        }
        else
        {
            db.outputs.position() = {0.0, 0.0};
            db.outputs.velocity() = {0.0, 0.0};
            db.outputs.isHovered() = false;
            db.outputs.isValid() = false;
        }

        return true;
    }
};

REGISTER_OGN_NODE()
} // ui
} // graph
} // omni
