/*
 * Copyright (C) 2020 Igalia S.L. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
 * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
 * THE POSSIBILITY OF SUCH DAMAGE.
 */

#include "config.h"
#include "WebXRInputSource.h"

#if ENABLE(WEBXR)

#include "EventNames.h"
#if ENABLE(GAMEPAD)
#include "Gamepad.h"
#include "WebXRGamepad.h"
#endif
#include "WebXRFrame.h"
#include "WebXRSession.h"
#include "XRInputSourceEvent.h"
#include <wtf/IsoMallocInlines.h>

namespace WebCore {

WTF_MAKE_ISO_ALLOCATED_IMPL(WebXRInputSource);

Ref<WebXRInputSource> WebXRInputSource::create(Document& document, WebXRSession& session, double timestamp, const PlatformXR::Device::FrameData::InputSource& source)
{
    return adoptRef(*new WebXRInputSource(document, session, timestamp, source));
}

WebXRInputSource::WebXRInputSource(Document& document, WebXRSession& session, double timestamp, const PlatformXR::Device::FrameData::InputSource& source)
    : m_session(session)
    , m_targetRaySpace(WebXRInputSpace::create(document, session, source.pointerOrigin))
    , m_connectTime(timestamp)
#if ENABLE(GAMEPAD)
    , m_gamepad(Gamepad::create(WebXRGamepad(timestamp, timestamp, source)))
#endif
{
    update(timestamp, source);
}

WebXRInputSource::~WebXRInputSource() = default;

WebXRSession* WebXRInputSource::session()
{
    return m_session.get();
}

void WebXRInputSource::update(double timestamp, const PlatformXR::Device::FrameData::InputSource& source)
{
    RefPtr session = m_session.get();
    if (!session)
        return;

#if !ENABLE(GAMEPAD)
    UNUSED_PARAM(timestamp);
#endif
    m_source = source;
    m_targetRaySpace->setPose(source.pointerOrigin);
    if (auto gripOrigin = source.gripOrigin) {
        if (m_gripSpace)
            m_gripSpace->setPose(*gripOrigin);
        else if (auto* document = downcast<Document>(session->scriptExecutionContext()))
            m_gripSpace = WebXRInputSpace::create(*document, *session, *gripOrigin);
    } else
        m_gripSpace = nullptr;
#if ENABLE(GAMEPAD)
    m_gamepad->updateFromPlatformGamepad(WebXRGamepad(timestamp, m_connectTime, source));
#endif

#if ENABLE(WEBXR_HANDS)
    if (source.handJoints) {
        // https://www.w3.org/TR/webxr-hand-input-1/#xrinputsource-interface
        // If the XRInputSource belongs to an XRSession that has not been requested
        // with the "hand-tracking" feature descriptor, hand MUST be null.
        if (!m_hand && session->isHandTrackingEnabled())
            m_hand = WebXRHand::create(*this);

        if (m_hand)
            m_hand->updateFromInputSource(source);

        return;
    }
#endif
}

bool WebXRInputSource::requiresInputSourceChange(const InputSource& source)
{
    return m_source.handeness != source.handeness
        || m_source.targetRayMode != source.targetRayMode
        || m_source.profiles != source.profiles
        || static_cast<bool>(m_gripSpace) != source.gripOrigin.has_value();
}

void WebXRInputSource::disconnect()
{
    m_connected = false;
#if ENABLE(GAMEPAD)
    m_gamepad->setConnected(false);
#endif
}

void WebXRInputSource::pollEvents(Vector<Ref<XRInputSourceEvent>>& events)
{
    RefPtr session = m_session.get();
    if (!session)
        return;

    auto createEvent = [this, session](const AtomString& name) -> Ref<XRInputSourceEvent> {
        XRInputSourceEvent::Init init;
        init.frame = WebXRFrame::create(*session, WebXRFrame::IsAnimationFrame::No);
        init.inputSource = RefPtr { this };

        return XRInputSourceEvent::create(name, init);
    };

    if (!m_connected) {
        // A user agent MUST dispatch a selectend event on an XRSession when one of its XRInputSources ends 
        // when an XRInputSource that has begun a primary select action is disconnected.
        if (m_selectStarted)
            events.append(createEvent(eventNames().selectendEvent));

        // A user agent MUST dispatch a squeezeend event on an XRSession when one of its XRInputSources ends 
        // when an XRInputSource that has begun a primary squeeze action is disconnected.
        if (m_squeezeStarted)
            events.append(createEvent(eventNames().squeezeendEvent));

        return;
    }

    // https://immersive-web.github.io/webxr-gamepads-module/#xr-standard-gamepad-mapping
    // Button indices are based on the xr-standard layout.
    bool selectStarted = !m_source.buttons.isEmpty() && m_source.buttons.first().pressed;
    bool squeezeStarted = m_source.buttons.size() > 1 && m_source.buttons[1].pressed;

    if (selectStarted != m_selectStarted) {
        if (selectStarted) {
            // A user agent MUST dispatch a selectstart event on an XRSession when one of its XRInputSources begins its primary action.
            events.append(createEvent(eventNames().selectstartEvent));
        } else {
            // A user agent MUST dispatch a selectend event on an XRSession when one of its XRInputSources ends its primary action.
            // A user agent MUST dispatch a select event on an XRSession when one of its XRInputSources has fully completed a primary action
            events.append(createEvent(eventNames().selectEvent));
            events.append(createEvent(eventNames().selectendEvent));
        }
        m_selectStarted = selectStarted;
    }

    if (squeezeStarted != m_squeezeStarted) {
        if (squeezeStarted) {
            // A user agent MUST dispatch a squeezestart event on an XRSession when one of its XRInputSources begins its primary squeeze action.
            events.append(createEvent(eventNames().squeezestartEvent));
        } else {
            // A user agent MUST dispatch a selectend event on an XRSession when one of its XRInputSources ends its primary squeeze action.
            // A user agent MUST dispatch a select event on an XRSession when one of its XRInputSources has fully completed a primary squeeze action.
            events.append(createEvent(eventNames().squeezeEvent));
            events.append(createEvent(eventNames().squeezeendEvent));
        }
        m_squeezeStarted = squeezeStarted;
    }
}

} // namespace WebCore

#endif // ENABLE(WEBXR)
