// 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 <OgnOnViewportDraggedDatabase.h>

#include "ViewportDragNodeCommon.h"

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

namespace omni
{
namespace graph
{
namespace ui
{
class OgnOnViewportDragged
{
public:
    struct InternalState
    {
        carb::events::ISubscriptionPtr dragBeganSub;
        carb::events::ISubscriptionPtr dragChangedSub;
        carb::events::ISubscriptionPtr dragEndedSub;
        ViewportDragEventPayloads eventPayloads;
        ViewportDragEventStates eventStates;
    } m_internalState;

    static void initialize(GraphContextObj const&, NodeObj const& nodeObj)
    {
        OgnOnViewportDragged& state = OgnOnViewportDraggedDatabase::sm_stateManagerOgnOnViewportDragged
            .getState<OgnOnViewportDragged>(nodeObj.nodeHandle);

        // Subscribe to drag events
        if (omni::kit::IApp* app = carb::getCachedInterface<omni::kit::IApp>())
        {
            state.m_internalState.dragBeganSub = carb::events::createSubscriptionToPushByType(
                app->getMessageBusEventStream(),
                kDragBeganEventType,
                [nodeObj](carb::events::IEvent* e)
                {
                    if (e)
                    {
                        OgnOnViewportDragged& state = OgnOnViewportDraggedDatabase::sm_stateManagerOgnOnViewportDragged
                            .getState<OgnOnViewportDragged>(nodeObj.nodeHandle);
                        state.m_internalState.eventPayloads.setDragBeganPayload(e->payload);

                        if (nodeObj.iNode->isValid(nodeObj))
                            nodeObj.iNode->requestCompute(nodeObj);
                    }
                }
            );
            state.m_internalState.dragChangedSub = carb::events::createSubscriptionToPushByType(
                app->getMessageBusEventStream(),
                kDragChangedEventType,
                [nodeObj](carb::events::IEvent* e)
                {
                    if (e)
                    {
                        OgnOnViewportDragged& state = OgnOnViewportDraggedDatabase::sm_stateManagerOgnOnViewportDragged
                            .getState<OgnOnViewportDragged>(nodeObj.nodeHandle);
                        state.m_internalState.eventPayloads.setDragChangedPayload(e->payload);
                    }
                }
            );
            state.m_internalState.dragEndedSub = carb::events::createSubscriptionToPushByType(
                app->getMessageBusEventStream(),
                kDragEndedEventType,
                [nodeObj](carb::events::IEvent* e)
                {
                    if (e)
                    {
                        OgnOnViewportDragged& state = OgnOnViewportDraggedDatabase::sm_stateManagerOgnOnViewportDragged
                            .getState<OgnOnViewportDragged>(nodeObj.nodeHandle);
                        state.m_internalState.eventPayloads.setDragEndedPayload(e->payload);

                        if (nodeObj.iNode->isValid(nodeObj))
                            nodeObj.iNode->requestCompute(nodeObj);
                    }
                }
            );
        }
    }

    static void release(const NodeObj& nodeObj)
    {
        OgnOnViewportDragged& state = OgnOnViewportDraggedDatabase::sm_stateManagerOgnOnViewportDragged
            .getState<OgnOnViewportDragged>(nodeObj.nodeHandle);

        // Unsubscribe from drag events
        if (state.m_internalState.dragBeganSub.get())
            state.m_internalState.dragBeganSub.detach()->unsubscribe();

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

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

    static bool compute(OgnOnViewportDraggedDatabase& db)
    {
        if (checkNodeDisabledForOnlyPlay(db))
            return true;

        OgnOnViewportDragged& state = db.internalState<OgnOnViewportDragged>();
        if (state.m_internalState.eventPayloads.empty())
            return true;

        // Get the targeted viewport and gesture
        char const* const viewportWindowName = db.tokenToString(db.inputs.viewport());
        char const* const gestureName = db.tokenToString(db.inputs.gesture());

        if (!omni::ui::Workspace::getWindow(viewportWindowName))
        {
            db.logWarning("Viewport window '%s' not found", viewportWindowName);
        }

        // Process event payloads and update event state
        bool dragBegan = false;
        for (auto const& dragBeganPayload : state.m_internalState.eventPayloads.dragBeganPayloads())
        {
            if (!dragBeganPayload.second.isValid)
                continue;

            auto& eventStateValue = state.m_internalState.eventStates[dragBeganPayload.first];
            eventStateValue.isDragInProgress = true;

            eventStateValue.initialPositionNorm = dragBeganPayload.second.initialPositionNorm;
            eventStateValue.initialPositionPixel = dragBeganPayload.second.initialPositionPixel;

            eventStateValue.currentPositionNorm = dragBeganPayload.second.currentPositionNorm;
            eventStateValue.currentPositionPixel = dragBeganPayload.second.currentPositionPixel;

            if (std::strcmp(viewportWindowName, dragBeganPayload.first.viewportWindowName) == 0
                && std::strcmp(gestureName, dragBeganPayload.first.gestureName) == 0)
            {
                dragBegan = true;
            }
        }

        for (auto const& dragChangedPayload : state.m_internalState.eventPayloads.dragChangedPayloads())
        {
            if (!dragChangedPayload.second.isValid)
                continue;

            auto& eventStateValue = state.m_internalState.eventStates[dragChangedPayload.first];
            if (eventStateValue.isDragInProgress)
            {
                eventStateValue.currentPositionNorm = dragChangedPayload.second.currentPositionNorm;
                eventStateValue.currentPositionPixel = dragChangedPayload.second.currentPositionPixel;
            }
        }

        bool dragEnded = false;
        for (auto const& dragEndedPayload : state.m_internalState.eventPayloads.dragEndedPayloads())
        {
            if (!dragEndedPayload.second.isValid)
                continue;

            auto& eventStateValue = state.m_internalState.eventStates[dragEndedPayload.first];
            if (eventStateValue.isDragInProgress)
            {
                eventStateValue.isDragInProgress = false;

                if (std::strcmp(viewportWindowName, dragEndedPayload.first.viewportWindowName) == 0
                    && std::strcmp(gestureName, dragEndedPayload.first.gestureName) == 0)
                {
                    dragEnded = true;
                }
            }
        }

        state.m_internalState.eventPayloads.clear();

        // Get event state and set outputs
        auto it = state.m_internalState.eventStates.find({viewportWindowName, gestureName});
        if (it != state.m_internalState.eventStates.end())
        {
            if (dragEnded)
            {
                db.outputs.began() = kExecutionAttributeStateDisabled;
                db.outputs.ended() = kExecutionAttributeStateEnabled;
                db.outputs.initialPosition() = db.inputs.useNormalizedCoords() ? it->second.initialPositionNorm : it->second.initialPositionPixel;
                db.outputs.finalPosition() = db.inputs.useNormalizedCoords() ? it->second.currentPositionNorm : it->second.currentPositionPixel;
            }
            else if (dragBegan)
            {
                db.outputs.began() = kExecutionAttributeStateEnabled;
                db.outputs.ended() = kExecutionAttributeStateDisabled;
                db.outputs.initialPosition() = db.inputs.useNormalizedCoords() ? it->second.initialPositionNorm : it->second.initialPositionPixel;
                db.outputs.finalPosition() = {0.0, 0.0};
            }
            else
            {
                db.outputs.began() = kExecutionAttributeStateDisabled;
                db.outputs.ended() = kExecutionAttributeStateDisabled;
            }
        }

        return true;
    }
};

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