/*
 * Copyright (C) 2018 Apple Inc. 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. ``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
 * 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 "PointerEvent.h"

#include "EventNames.h"
#include "MouseEventTypes.h"
#include "Node.h"
#include "PointerEventTypeNames.h"
#include <numbers>
#include <wtf/TZoneMallocInlines.h>

namespace WebCore {

WTF_MAKE_TZONE_OR_ISO_ALLOCATED_IMPL(PointerEvent);

AtomString PointerEvent::typeFromMouseEventType(const AtomString& mouseEventType)
{
    auto& names = eventNames();
    if (mouseEventType == names.auxclickEvent)
        return names.auxclickEvent;
    if (mouseEventType == names.clickEvent)
        return names.clickEvent;
    if (mouseEventType == names.contextmenuEvent)
        return names.contextmenuEvent;
    if (mouseEventType == names.mousedownEvent)
        return names.pointerdownEvent;
    if (mouseEventType == names.mouseoverEvent)
        return names.pointeroverEvent;
    if (mouseEventType == names.mouseenterEvent)
        return names.pointerenterEvent;
    if (mouseEventType == names.mousemoveEvent)
        return names.pointermoveEvent;
    if (mouseEventType == names.mouseleaveEvent)
        return names.pointerleaveEvent;
    if (mouseEventType == names.mouseoutEvent)
        return names.pointeroutEvent;
    if (mouseEventType == names.mouseupEvent)
        return names.pointerupEvent;

    return nullAtom();
}

RefPtr<PointerEvent> PointerEvent::create(MouseButton button, const MouseEvent& mouseEvent, PointerID pointerId, const String& pointerType)
{
    auto type = typeFromMouseEventType(mouseEvent.type());
    if (type.isEmpty())
        return nullptr;

    return create(type, button, mouseEvent, pointerId, pointerType);
}

Ref<PointerEvent> PointerEvent::create(const AtomString& type, MouseButton button, const MouseEvent& mouseEvent, PointerID pointerId, const String& pointerType)
{
    return create(type, button, mouseEvent, pointerId, pointerType, typeCanBubble(type), typeIsCancelable(type));
}

Ref<PointerEvent> PointerEvent::create(const AtomString& type, MouseButton button, const MouseEvent& mouseEvent, PointerID pointerId, const String& pointerType, CanBubble canBubble, IsCancelable isCancelable)
{
    return adoptRef(*new PointerEvent(type, button, mouseEvent, pointerId, pointerType, canBubble, isCancelable, typeIsComposed(type)));
}

Ref<PointerEvent> PointerEvent::create(const AtomString& type, PointerID pointerId, const String& pointerType, IsPrimary isPrimary)
{
    return adoptRef(*new PointerEvent(type, pointerId, pointerType, isPrimary));
}

PointerEvent::PointerEvent()
    : MouseEvent(EventInterfaceType::PointerEvent)
{
}

// Calculated in accordance with https://w3c.github.io/pointerevents/#converting-between-tiltx-tilty-and-altitudeangle-azimuthangle.
auto PointerEvent::angleFromTilt(long tiltX, long tiltY) -> PointerEventAngle
{
    const double tiltXRadians = tiltX * radiansPerDegreeDouble;
    const double tiltYRadians = tiltY * radiansPerDegreeDouble;
    double azimuthAngle = 0;

    if (!tiltX && tiltY)
        azimuthAngle = tiltY > 0 ? piOverTwoDouble : 3 * piOverTwoDouble;
    else if (tiltX && !tiltY)
        azimuthAngle = tiltX < 0 ? std::numbers::pi : azimuthAngle;
    else if (abs(tiltX) == 90 || abs(tiltY) == 90)
        azimuthAngle = 0;
    else {
        azimuthAngle = atan2(tan(tiltYRadians), tan(tiltXRadians));
        azimuthAngle = azimuthAngle < 0 ? azimuthAngle + radiansPerTurnDouble : azimuthAngle;
    }

    double altitudeAngle = 0;

    if (abs(tiltX) == 90 || abs(tiltY) == 90)
        altitudeAngle = 0;
    else if (!tiltX)
        altitudeAngle = piOverTwoDouble - abs(tiltYRadians);
    else if (!tiltY)
        altitudeAngle = piOverTwoDouble - abs(tiltXRadians);
    else
        altitudeAngle =  atan(1.0 / sqrt(pow(tan(tiltXRadians), 2) + pow(tan(tiltYRadians), 2)));

    return { altitudeAngle, azimuthAngle };
}

// This algorithm is equivalent to the one provided in https://w3c.github.io/pointerevents/#converting-between-tiltx-tilty-and-altitudeangle-azimuthangle.
auto PointerEvent::tiltFromAngle(double altitudeAngle, double azimuthAngle) -> PointerEventTilt
{
    double tiltXRadians = WTF::areEssentiallyEqual(altitudeAngle, 0.0) ? cos(azimuthAngle) * piOverTwoDouble : atan(cos(azimuthAngle) / tan(altitudeAngle));
    double tiltYRadians = WTF::areEssentiallyEqual(altitudeAngle, 0.0) ? sin(azimuthAngle) * piOverTwoDouble : atan(sin(azimuthAngle) / tan(altitudeAngle));

    return { static_cast<long>(round(tiltXRadians * degreesPerRadianDouble)), static_cast<long>(round(tiltYRadians * degreesPerRadianDouble)) };
}

PointerEvent::PointerEvent(const AtomString& type, Init&& initializer, IsTrusted isTrusted)
    : MouseEvent(EventInterfaceType::PointerEvent, type, initializer, isTrusted)
    , m_pointerId(initializer.pointerId)
    , m_width(initializer.width)
    , m_height(initializer.height)
    , m_pressure(initializer.pressure)
    , m_tangentialPressure(initializer.tangentialPressure)
    , m_tiltX(initializer.tiltX)
    , m_tiltY(initializer.tiltY)
    , m_twist(initializer.twist)
    , m_altitudeAngle(initializer.altitudeAngle)
    , m_azimuthAngle(initializer.azimuthAngle)
    , m_pointerType(initializer.pointerType)
    , m_isPrimary(initializer.isPrimary)
    , m_coalescedEvents(initializer.coalescedEvents)
    , m_predictedEvents(initializer.predictedEvents)
{
    // A pair of components is complete when both or no values have been initialized to non-default values.
    bool tiltComponentsComplete = !initializer.tiltX == !initializer.tiltY;
    bool angleComponentsComplete = !initializer.azimuthAngle == (initializer.altitudeAngle == piOverTwoDouble);

    PointerEventAngle angle = angleFromTilt(m_tiltX, m_tiltY);
    PointerEventTilt tilt = tiltFromAngle(m_altitudeAngle, m_azimuthAngle);

    m_altitudeAngle = angleComponentsComplete ? angle.altitudeAngle : m_altitudeAngle;
    m_azimuthAngle = angleComponentsComplete ? angle.azimuthAngle : m_azimuthAngle;
    m_tiltX = tiltComponentsComplete ? tilt.tiltX : m_tiltX;
    m_tiltY = tiltComponentsComplete ? tilt.tiltY : m_tiltY;
}

static Vector<Ref<PointerEvent>> createCoalescedPointerEvents(const AtomString& type, MouseButton button, const MouseEvent& mouseEvent, PointerID pointerId, const String& pointerType)
{
    if (type != eventNames().pointermoveEvent)
        return { };

    // Populated in accordance with the https://www.w3.org/TR/pointerevents3/#populating-and-maintaining-the-coalesced-and-predicted-events-lists spec.

    Vector<Ref<PointerEvent>> result;
    for (Ref coalescedMouseEvent : mouseEvent.coalescedEvents()) {
        Ref pointerEvent = PointerEvent::create(type, button, coalescedMouseEvent.get(), pointerId, pointerType, Event::CanBubble::No, Event::IsCancelable::No);
        result.append(WTFMove(pointerEvent));
    }

    return result;
}

static Vector<Ref<PointerEvent>> createPredictedPointerEvents(const AtomString& type, MouseButton button, const MouseEvent& mouseEvent, PointerID pointerId, const String& pointerType)
{
    if (type != eventNames().pointermoveEvent)
        return { };

    // Populated in accordance with the https://www.w3.org/TR/pointerevents3/#populating-and-maintaining-the-coalesced-and-predicted-events-lists spec.

    Vector<Ref<PointerEvent>> result;
    for (Ref predictedMouseEvent : mouseEvent.predictedEvents()) {
        Ref pointerEvent = PointerEvent::create(type, button, predictedMouseEvent.get(), pointerId, pointerType, Event::CanBubble::No, Event::IsCancelable::No);
        result.append(WTFMove(pointerEvent));
    }

    return result;
}

PointerEvent::PointerEvent(const AtomString& type, MouseButton button, const MouseEvent& mouseEvent, PointerID pointerId, const String& pointerType, CanBubble canBubble, IsCancelable isCancelable, IsComposed isComposed)
    : MouseEvent(EventInterfaceType::PointerEvent, type, canBubble, isCancelable, isComposed, mouseEvent.timeStamp(), mouseEvent.view(), mouseEvent.detail(), mouseEvent.screenLocation(),
        mouseEvent.windowLocation(), mouseEvent.movementX(), mouseEvent.movementY(), mouseEvent.modifierKeys(), button, mouseEvent.buttons(),
        mouseEvent.relatedTarget(), 0, mouseEvent.syntheticClickType(), { }, { }, mouseEvent.isSimulated() ? IsSimulated::Yes : IsSimulated::No, mouseEvent.isTrusted() ? IsTrusted::Yes : IsTrusted::No)
    , m_pointerId(pointerId)
    // MouseEvent is a misnomer in this context, and can represent events from a pressure sensitive input device if the pointer type is "Pen" or "Touch".
    // If it does represent a pressure sensitive input device, we consult MouseEvent::force() for the event pressure, else we fall back to spec defaults.
    , m_pressure(pointerType != mousePointerEventType() ? std::clamp(mouseEvent.force(), 0., 1.) : pressureForPressureInsensitiveInputDevices(buttons()))
    , m_pointerType(pointerType)
    , m_isPrimary(true)
    , m_coalescedEvents(createCoalescedPointerEvents(type, button, mouseEvent, pointerId, pointerType))
    , m_predictedEvents(createPredictedPointerEvents(type, button, mouseEvent, pointerId, pointerType))
{
}

PointerEvent::PointerEvent(const AtomString& type, PointerID pointerId, const String& pointerType, IsPrimary isPrimary)
    : MouseEvent(EventInterfaceType::PointerEvent, type, typeCanBubble(type), typeIsCancelable(type), typeIsComposed(type), MonotonicTime::now(), nullptr, 0, { }, { }, 0, 0, { }, buttonForType(type), buttonsForType(type), SyntheticClickType::NoTap, nullptr)
    , m_pointerId(pointerId)
    // FIXME: This may be wrong because we can create an event from a pressure sensitive device.
    // We don't have a backing MouseEvent to consult pressure/force information from, though, so let's do the next best thing.
    , m_pressure(pressureForPressureInsensitiveInputDevices(buttons()))
    , m_pointerType(pointerType)
    , m_isPrimary(isPrimary == IsPrimary::Yes)
{
}

PointerEvent::~PointerEvent() = default;

Vector<Ref<PointerEvent>> PointerEvent::getCoalescedEvents() const
{
#if ASSERT_ENABLED
    if (isTrusted()) {
        auto& names = eventNames();
        if (type() == names.pointermoveEvent)
            ASSERT(!m_coalescedEvents.isEmpty(), "Trusted pointermove events must have more than zero coalesced events.");
        else
            ASSERT(m_coalescedEvents.isEmpty(), "Trusted non-pointermove events must have zero coalesced events.");

        auto timestampsAreMonotonicallyIncreasing = std::ranges::is_sorted(m_coalescedEvents, [](const auto& previousEvent, const auto& newEvent) {
            return previousEvent->timeStamp() <= newEvent->timeStamp();
        });

        ASSERT(timestampsAreMonotonicallyIncreasing, "Coalesced event timestamps are not monotonically increasing.");

        auto canBubbleOrIsCancelable = std::ranges::any_of(m_coalescedEvents, [](const auto& event) {
            return event->bubbles() || event->cancelable();
        });

        ASSERT(!canBubbleOrIsCancelable, "Coalesced events must not bubble and must not be cancelable.");
    }
#endif

    return m_coalescedEvents;
}

Vector<Ref<PointerEvent>> PointerEvent::getPredictedEvents() const
{
#if ASSERT_ENABLED
    if (isTrusted()) {
        auto& names = eventNames();
        if (type() != names.pointermoveEvent)
            ASSERT(m_predictedEvents.isEmpty(), "Trusted non-pointermove events must have zero predicted events.");

        auto timestampsAreMonotonicallyIncreasing = std::ranges::is_sorted(m_predictedEvents, [](const auto& previousEvent, const auto& newEvent) {
            return previousEvent->timeStamp() <= newEvent->timeStamp();
        });

        ASSERT(timestampsAreMonotonicallyIncreasing, "Predicted event timestamps are not monotonically increasing.");

        if (!m_predictedEvents.isEmpty())
            ASSERT(m_predictedEvents.first()->timeStamp() >= this->timeStamp(), "Predicted events must have a timestamp greater than or equal to the dispatched event.");

        auto canBubbleOrIsCancelable = std::ranges::any_of(m_predictedEvents, [](const auto& event) {
            return event->bubbles() || event->cancelable();
        });

        ASSERT(!canBubbleOrIsCancelable, "Predicted events must not bubble and must not be cancelable.");
    }
#endif

    return m_predictedEvents;
}

void PointerEvent::receivedTarget()
{
    MouseRelatedEvent::receivedTarget();

    if (!isTrusted())
        return;

    for (Ref coalescedEvent : m_coalescedEvents)
        coalescedEvent->setTarget(this->target());

    for (Ref predictedEvent : m_predictedEvents)
        predictedEvent->setTarget(this->target());
}

} // namespace WebCore
