/*
 * Copyright (C) 2013 Samsung Electronics. All rights reserved.
 * Copyright (C) 2013 Nokia Corporation and/or its subsidiary(-ies)
 *
 * 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 "GestureRecognizer.h"

#include "EasingCurves.h"
#include "EwkView.h"
#include "NotImplemented.h"
#include "WKSharedAPICast.h"

#if ENABLE(TOUCH_EVENTS)

using namespace WebCore;

namespace WebKit {

class GestureHandler {
public:
    explicit GestureHandler(EwkView*);
    ~GestureHandler();

    EwkView* view() { return m_ewkView; }

    void reset();
    void handleSingleTap(const IntPoint&);
    void handleDoubleTap(const IntPoint&);
    void handleTapAndHold(const IntPoint&);
    void handlePanStarted(const IntPoint&);
    void handlePan(const IntPoint&, double timestamp);
    void handlePanFinished();
    void handleFlick(const IntSize&);
    void handlePinchStarted(const Vector<IntPoint>&);
    void handlePinch(const Vector<IntPoint>&);
    void handlePinchFinished();

private:
    static Eina_Bool panAnimatorCallback(void*);
    static Eina_Bool flickAnimatorCallback(void*);

    static const int s_historyCapacity = 5;
    static constexpr float s_flickTime = 1.0;

    EwkView* m_ewkView;
    IntPoint m_lastPoint;
    IntPoint m_currentPoint;
    bool m_isCurrentPointUpdated;
    Ecore_Animator* m_panAnimator;
    Ecore_Animator* m_flickAnimator;
    IntSize m_flickOffset;
    unsigned m_flickDuration;
    unsigned m_flickIndex;

    struct HistoryItem {
        IntPoint point;
        double timestamp;
    };
    Vector<HistoryItem> m_history;
    size_t m_oldestHistoryItemIndex;
};

GestureHandler::GestureHandler(EwkView* ewkView)
    : m_ewkView(ewkView)
    , m_isCurrentPointUpdated(false)
    , m_panAnimator(0)
    , m_flickAnimator(0)
    , m_flickDuration(0)
    , m_flickIndex(0)
    , m_oldestHistoryItemIndex(0)
{
    m_history.reserveInitialCapacity(s_historyCapacity);
}

GestureHandler::~GestureHandler()
{
    reset();
}

void GestureHandler::reset()
{
    if (m_panAnimator) {
        ecore_animator_del(m_panAnimator);
        m_panAnimator = 0;

        m_oldestHistoryItemIndex = 0;
        if (m_history.size())
            m_history.resize(0);
    }

    if (m_flickAnimator) {
        ecore_animator_del(m_flickAnimator);
        m_flickAnimator = 0;
    }
}

void GestureHandler::handleSingleTap(const IntPoint& position)
{
    Evas* evas = evas_object_evas_get(m_ewkView->evasObject());
    ASSERT(evas);

    // Send mouse move, down and up event to create fake click event.
    Evas_Event_Mouse_Move mouseMove;
    mouseMove.buttons = 0;
    mouseMove.prev.output.x = mouseMove.prev.canvas.x = position.x();
    mouseMove.prev.output.y = mouseMove.prev.canvas.y = position.y();
    mouseMove.cur.output.x = mouseMove.cur.canvas.x = position.x();
    mouseMove.cur.output.y = mouseMove.cur.canvas.y = position.y();
    mouseMove.data = 0;
    mouseMove.modifiers = const_cast<Evas_Modifier*>(evas_key_modifier_get(evas));
    mouseMove.locks = const_cast<Evas_Lock*>(evas_key_lock_get(evas));
    mouseMove.timestamp = ecore_loop_time_get();
    mouseMove.event_flags = EVAS_EVENT_FLAG_NONE;
    mouseMove.dev = 0;
    m_ewkView->webView()->sendMouseEvent(&mouseMove);

    Evas_Event_Mouse_Down mouseDown;
    mouseDown.button = 1;
    mouseDown.output.x = mouseDown.canvas.x = position.x();
    mouseDown.output.y = mouseDown.canvas.y = position.y();
    mouseDown.data = 0;
    mouseDown.modifiers = const_cast<Evas_Modifier*>(evas_key_modifier_get(evas));
    mouseDown.locks = const_cast<Evas_Lock*>(evas_key_lock_get(evas));
    mouseDown.flags = EVAS_BUTTON_NONE;
    mouseDown.timestamp = ecore_loop_time_get();
    mouseDown.event_flags = EVAS_EVENT_FLAG_NONE;
    mouseDown.dev = 0;
    m_ewkView->webView()->sendMouseEvent(&mouseDown);

    Evas_Event_Mouse_Up mouseUp;
    mouseUp.button = 1;
    mouseUp.output.x = mouseUp.canvas.x = position.x();
    mouseUp.output.y = mouseUp.canvas.y = position.y();
    mouseUp.data = 0;
    mouseUp.modifiers = const_cast<Evas_Modifier*>(evas_key_modifier_get(evas));
    mouseUp.locks = const_cast<Evas_Lock*>(evas_key_lock_get(evas));
    mouseUp.flags = EVAS_BUTTON_NONE;
    mouseUp.timestamp = ecore_loop_time_get();
    mouseUp.event_flags = EVAS_EVENT_FLAG_NONE;
    mouseUp.dev = 0;
    m_ewkView->webView()->sendMouseEvent(&mouseUp);
}

void GestureHandler::handleDoubleTap(const IntPoint&)
{
    notImplemented();
}

void GestureHandler::handleTapAndHold(const IntPoint&)
{
    notImplemented();
}

Eina_Bool GestureHandler::panAnimatorCallback(void* data)
{
    GestureHandler* gestureHandler = static_cast<GestureHandler*>(data);
    if (!gestureHandler->m_isCurrentPointUpdated || gestureHandler->m_lastPoint == gestureHandler->m_currentPoint)
        return ECORE_CALLBACK_RENEW;

    gestureHandler->view()->scrollBy(gestureHandler->m_lastPoint - gestureHandler->m_currentPoint);
    gestureHandler->m_lastPoint = gestureHandler->m_currentPoint;
    gestureHandler->m_isCurrentPointUpdated = false;

    return ECORE_CALLBACK_RENEW;
}

void GestureHandler::handlePanStarted(const IntPoint& point)
{
    ASSERT(!m_panAnimator);
    m_panAnimator = ecore_animator_add(panAnimatorCallback, this);
    m_lastPoint = m_currentPoint = point;
}

void GestureHandler::handlePan(const IntPoint& point, double timestamp)
{
    m_currentPoint = point;
    m_isCurrentPointUpdated = true;

    // Save current point to use to calculate offset of flick.
    HistoryItem item = { m_currentPoint, timestamp };
    if (m_history.size() < m_history.capacity())
        m_history.uncheckedAppend(item);
    else {
        m_history[m_oldestHistoryItemIndex++] = item;
        if (m_oldestHistoryItemIndex == m_history.capacity())
            m_oldestHistoryItemIndex = 0;
    }
}

void GestureHandler::handlePanFinished()
{
    ASSERT(m_panAnimator);
    ecore_animator_del(m_panAnimator);
    m_panAnimator = 0;

    if (!m_history.isEmpty()) {
        // Calculate offset to move during one frame.
        const HistoryItem& oldestHistoryItem = m_history[m_oldestHistoryItemIndex];
        double frame = (ecore_time_get() - oldestHistoryItem.timestamp) / ecore_animator_frametime_get();
        IntSize offset = oldestHistoryItem.point - m_currentPoint;
        offset.scale(1 / frame);
        handleFlick(offset);
    }

    m_oldestHistoryItemIndex = 0;
    if (m_history.size())
        m_history.resize(0);
}

Eina_Bool GestureHandler::flickAnimatorCallback(void* data)
{
    GestureHandler* gestureHandler = static_cast<GestureHandler*>(data);
    float multiplier = easeInOutQuad(gestureHandler->m_flickIndex, 0, s_flickTime, gestureHandler->m_flickDuration);
    float offsetWidth = gestureHandler->m_flickOffset.width() * multiplier;
    float offsetHeight = gestureHandler->m_flickOffset.height() * multiplier;
    offsetWidth = (offsetWidth > 0) ? ceilf(offsetWidth) : floorf(offsetWidth);
    offsetHeight = (offsetHeight > 0) ? ceilf(offsetHeight) : floorf(offsetHeight);
    IntSize offset(offsetWidth, offsetHeight);
    gestureHandler->m_flickIndex--;

    if (offset.isZero() || !gestureHandler->view()->scrollBy(offset) || !gestureHandler->m_flickIndex) {
        gestureHandler->m_flickAnimator = 0;
        return ECORE_CALLBACK_CANCEL;
    }

    return ECORE_CALLBACK_RENEW;
}

void GestureHandler::handleFlick(const IntSize& offset)
{
    m_flickOffset = offset;
    m_flickIndex = m_flickDuration = s_flickTime / ecore_animator_frametime_get();
    m_flickAnimator = ecore_animator_add(flickAnimatorCallback, this);
}

void GestureHandler::handlePinchStarted(const Vector<IntPoint>&)
{
    notImplemented();
}

void GestureHandler::handlePinch(const Vector<IntPoint>&)
{
    notImplemented();
}

void GestureHandler::handlePinchFinished()
{
    notImplemented();
}

const double GestureRecognizer::s_doubleTapTimeoutInSeconds = 0.4;
const double GestureRecognizer::s_tapAndHoldTimeoutInSeconds = 1.0;
const int GestureRecognizer::s_squaredDoubleTapThreshold = 10000;
const int GestureRecognizer::s_squaredPanThreshold = 100;

GestureRecognizer::GestureRecognizer(EwkView* ewkView)
    : m_recognizerFunction(&GestureRecognizer::noGesture)
    , m_gestureHandler(std::make_unique<GestureHandler>(ewkView))
    , m_tapAndHoldTimer(0)
    , m_doubleTapTimer(0)
{
}

GestureRecognizer::~GestureRecognizer()
{
}

void GestureRecognizer::processTouchEvent(WKTouchEventRef eventRef)
{
    WKEventType type = WKTouchEventGetType(eventRef);
    if (type == kWKEventTypeTouchCancel) {
        reset();
        return;
    }

    (this->*m_recognizerFunction)(eventRef);
}

Eina_Bool GestureRecognizer::doubleTapTimerCallback(void* data)
{
    GestureRecognizer* gestureRecognizer = static_cast<GestureRecognizer*>(data);
    gestureRecognizer->m_doubleTapTimer = 0;

    // If doubleTapTimer is fired we should not process double tap,
    // so process single tap here if touched point is already released
    // or do nothing for processing single tap when touch is released.
    if (gestureRecognizer->m_recognizerFunction == &GestureRecognizer::doubleTapGesture) {
        gestureRecognizer->m_gestureHandler->handleSingleTap(gestureRecognizer->m_firstPressedPoint);
        gestureRecognizer->m_recognizerFunction = &GestureRecognizer::noGesture;
    }

    return ECORE_CALLBACK_CANCEL;
}

Eina_Bool GestureRecognizer::tapAndHoldTimerCallback(void* data)
{
    GestureRecognizer* gestureRecognizer = static_cast<GestureRecognizer*>(data);
    gestureRecognizer->m_tapAndHoldTimer = 0;
    gestureRecognizer->m_gestureHandler->handleTapAndHold(gestureRecognizer->m_firstPressedPoint);
    gestureRecognizer->m_recognizerFunction = &GestureRecognizer::noGesture;

    return ECORE_CALLBACK_CANCEL;
}

inline bool GestureRecognizer::exceedsPanThreshold(const IntPoint& first, const IntPoint& last) const
{
    return first.distanceSquaredToPoint(last) > s_squaredPanThreshold;
}

inline bool GestureRecognizer::exceedsDoubleTapThreshold(const IntPoint& first, const IntPoint& last) const
{
    return first.distanceSquaredToPoint(last) > s_squaredDoubleTapThreshold;
}

static inline WKPoint getPointAtIndex(WKArrayRef array, size_t index)
{
    WKTouchPointRef pointRef = static_cast<WKTouchPointRef>(WKArrayGetItemAtIndex(array, index));
    ASSERT(pointRef);

    return WKTouchPointGetPosition(pointRef);
}

static inline Vector<IntPoint> createVectorWithWKArray(WKArrayRef array, size_t size)
{
    Vector<IntPoint> points;
    points.reserveCapacity(size);
    for (size_t i = 0; i < size; ++i)
        points.uncheckedAppend(toIntPoint(getPointAtIndex(array, i)));

    return points;
}

void GestureRecognizer::noGesture(WKTouchEventRef eventRef)
{
    switch (WKTouchEventGetType(eventRef)) {
    case kWKEventTypeTouchStart: {
        WKArrayRef touchPoints = WKTouchEventGetTouchPoints(eventRef);
        switch (WKArrayGetSize(touchPoints)) {
        case 1:
            m_gestureHandler->reset();
            m_recognizerFunction = &GestureRecognizer::singleTapGesture;
            m_firstPressedPoint = toIntPoint(getPointAtIndex(touchPoints, 0));
            ASSERT(!m_tapAndHoldTimer);
            m_tapAndHoldTimer = ecore_timer_add(s_tapAndHoldTimeoutInSeconds, tapAndHoldTimerCallback, this);
            m_doubleTapTimer = ecore_timer_add(s_doubleTapTimeoutInSeconds, doubleTapTimerCallback, this);
            break;
        case 2:
            m_recognizerFunction = &GestureRecognizer::pinchGesture;
            m_gestureHandler->handlePinchStarted(createVectorWithWKArray(touchPoints, 2));
            break;
        default:
            // There's no defined gesture when we touch three or more points.
            notImplemented();
            break;
        }
        break;
    }
    case kWKEventTypeTouchMove:
    case kWKEventTypeTouchEnd:
        break;
    default:
        ASSERT_NOT_REACHED();
        break;
    }
}

void GestureRecognizer::singleTapGesture(WKTouchEventRef eventRef)
{
    WKArrayRef touchPoints = WKTouchEventGetTouchPoints(eventRef);

    switch (WKTouchEventGetType(eventRef)) {
    case kWKEventTypeTouchStart:
        stopTapTimers();
        m_recognizerFunction = &GestureRecognizer::pinchGesture;
        m_gestureHandler->handlePinchStarted(createVectorWithWKArray(touchPoints, 2));
        break;
    case kWKEventTypeTouchMove: {
        IntPoint currentPoint = toIntPoint(getPointAtIndex(touchPoints, 0));
        if (exceedsPanThreshold(m_firstPressedPoint, currentPoint)) {
            stopTapTimers();
            m_recognizerFunction = &GestureRecognizer::panGesture;
            m_gestureHandler->handlePanStarted(currentPoint);
        }
        break;
    }
    case kWKEventTypeTouchEnd:
        if (m_tapAndHoldTimer) {
            ecore_timer_del(m_tapAndHoldTimer);
            m_tapAndHoldTimer = 0;
        }

        if (m_doubleTapTimer)
            m_recognizerFunction = &GestureRecognizer::doubleTapGesture;
        else {
            m_gestureHandler->handleSingleTap(m_firstPressedPoint);
            m_recognizerFunction = &GestureRecognizer::noGesture;
        }
        break;
    default:
        ASSERT_NOT_REACHED();
        break;
    }
}

void GestureRecognizer::doubleTapGesture(WKTouchEventRef eventRef)
{
    WKArrayRef touchPoints = WKTouchEventGetTouchPoints(eventRef);

    switch (WKTouchEventGetType(eventRef)) {
    case kWKEventTypeTouchStart: {
        if (m_doubleTapTimer) {
            ecore_timer_del(m_doubleTapTimer);
            m_doubleTapTimer = 0;
        }

        size_t numberOfTouchPoints = WKArrayGetSize(touchPoints);
        if (numberOfTouchPoints == 1) {
            if (exceedsDoubleTapThreshold(m_firstPressedPoint, toIntPoint(getPointAtIndex(touchPoints, 0))))
                m_recognizerFunction = &GestureRecognizer::singleTapGesture;
        } else {
            m_recognizerFunction = &GestureRecognizer::pinchGesture;
            m_gestureHandler->handlePinchStarted(createVectorWithWKArray(touchPoints, 2));
        }
        break;
    }
    case kWKEventTypeTouchMove: {
        IntPoint currentPoint = toIntPoint(getPointAtIndex(touchPoints, 0));
        if (exceedsPanThreshold(m_firstPressedPoint, currentPoint)) {
            m_recognizerFunction = &GestureRecognizer::panGesture;
            m_gestureHandler->handlePanStarted(currentPoint);
        }
        break;
    }
    case kWKEventTypeTouchEnd:
        m_gestureHandler->handleDoubleTap(m_firstPressedPoint);
        m_recognizerFunction = &GestureRecognizer::noGesture;
        break;
    default:
        ASSERT_NOT_REACHED();
        break;
    }
}

void GestureRecognizer::panGesture(WKTouchEventRef eventRef)
{
    WKArrayRef touchPoints = WKTouchEventGetTouchPoints(eventRef);

    switch (WKTouchEventGetType(eventRef)) {
    case kWKEventTypeTouchStart:
        m_recognizerFunction = &GestureRecognizer::pinchGesture;
        m_gestureHandler->handlePinchStarted(createVectorWithWKArray(touchPoints, 2));
        break;
    case kWKEventTypeTouchMove:
        m_gestureHandler->handlePan(toIntPoint(getPointAtIndex(touchPoints, 0)), WKTouchEventGetTimestamp(eventRef));
        break;
    case kWKEventTypeTouchEnd:
        m_gestureHandler->handlePanFinished();
        m_recognizerFunction = &GestureRecognizer::noGesture;
        break;
    default:
        ASSERT_NOT_REACHED();
        break;
    }
}

void GestureRecognizer::pinchGesture(WKTouchEventRef eventRef)
{
    WKArrayRef touchPoints = WKTouchEventGetTouchPoints(eventRef);
    size_t numberOfTouchPoints = WKArrayGetSize(touchPoints);
    ASSERT(numberOfTouchPoints >= 2);

    switch (WKTouchEventGetType(eventRef)) {
    case kWKEventTypeTouchMove: {
        m_gestureHandler->handlePinch(createVectorWithWKArray(touchPoints, 2));
        break;
    }
    case kWKEventTypeTouchEnd:
        if (numberOfTouchPoints == 2) {
            m_gestureHandler->handlePinchFinished();
            m_recognizerFunction = &GestureRecognizer::panGesture;
            WKTouchPointRef pointRef;
            for (size_t i = 0; i < numberOfTouchPoints; ++i) {
                pointRef = static_cast<WKTouchPointRef>(WKArrayGetItemAtIndex(touchPoints, i));
                WKTouchPointState state = WKTouchPointGetState(pointRef);
                if (state != kWKTouchPointStateTouchReleased && state != kWKTouchPointStateTouchCancelled)
                    break;
            }
            ASSERT(pointRef);
            m_gestureHandler->handlePanStarted(toIntPoint(WKTouchPointGetPosition(pointRef)));
        }
        break;
    case kWKEventTypeTouchStart:
        break;
    default:
        ASSERT_NOT_REACHED();
        break;
    }
}

void GestureRecognizer::reset()
{
    stopTapTimers();
    m_gestureHandler->reset();

    m_recognizerFunction = &GestureRecognizer::noGesture;
}

void GestureRecognizer::stopTapTimers()
{
    if (m_doubleTapTimer) {
        ecore_timer_del(m_doubleTapTimer);
        m_doubleTapTimer = 0;
    }

    if (m_tapAndHoldTimer) {
        ecore_timer_del(m_tapAndHoldTimer);
        m_tapAndHoldTimer = 0;
    }
}

} // namespace WebKit

#endif // ENABLE(TOUCH_EVENTS)
