/*
 * Copyright 2013 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include "gestureDetector.h"

//--------------------------------------------------------------------------------
// gestureDetector.cpp
//--------------------------------------------------------------------------------
namespace ndk_helper
{

//--------------------------------------------------------------------------------
// includes
//--------------------------------------------------------------------------------

//--------------------------------------------------------------------------------
// GestureDetector
//--------------------------------------------------------------------------------
GestureDetector::GestureDetector()
{
    dp_factor_ = 1.f;
}

void GestureDetector::SetConfiguration( AConfiguration* config )
{
    dp_factor_ = 160.f / AConfiguration_getDensity( config );
}

//--------------------------------------------------------------------------------
// TapDetector
//--------------------------------------------------------------------------------
GESTURE_STATE TapDetector::Detect( const AInputEvent* motion_event )
{
    if( AMotionEvent_getPointerCount( motion_event ) > 1 )
    {
        //Only support single touch
        return false;
    }

    int32_t action = AMotionEvent_getAction( motion_event );
    unsigned int flags = action & AMOTION_EVENT_ACTION_MASK;
    switch( flags )
    {
    case AMOTION_EVENT_ACTION_DOWN:
        down_pointer_id_ = AMotionEvent_getPointerId( motion_event, 0 );
        down_x_ = AMotionEvent_getX( motion_event, 0 );
        down_y_ = AMotionEvent_getY( motion_event, 0 );
        break;
    case AMOTION_EVENT_ACTION_UP:
    {
        int64_t eventTime = AMotionEvent_getEventTime( motion_event );
        int64_t downTime = AMotionEvent_getDownTime( motion_event );
        if( eventTime - downTime <= TAP_TIMEOUT )
        {
            if( down_pointer_id_ == AMotionEvent_getPointerId( motion_event, 0 ) )
            {
                float x = AMotionEvent_getX( motion_event, 0 ) - down_x_;
                float y = AMotionEvent_getY( motion_event, 0 ) - down_y_;
                if( x * x + y * y < TOUCH_SLOP * TOUCH_SLOP * dp_factor_ )
                {
                    LOGI( "TapDetector: Tap detected" );
                    return GESTURE_STATE_ACTION;
                }
            }
        }
        break;
    }
    }
    return GESTURE_STATE_NONE;
}

//--------------------------------------------------------------------------------
// DoubletapDetector
//--------------------------------------------------------------------------------
GESTURE_STATE DoubletapDetector::Detect( const AInputEvent* motion_event )
{
    if( AMotionEvent_getPointerCount( motion_event ) > 1 )
    {
        //Only support single double tap
        return false;
    }

    bool tap_detected = tap_detector_.Detect( motion_event );

    int32_t action = AMotionEvent_getAction( motion_event );
    unsigned int flags = action & AMOTION_EVENT_ACTION_MASK;
    switch( flags )
    {
    case AMOTION_EVENT_ACTION_DOWN:
    {
        int64_t eventTime = AMotionEvent_getEventTime( motion_event );
        if( eventTime - last_tap_time_ <= DOUBLE_TAP_TIMEOUT )
        {
            float x = AMotionEvent_getX( motion_event, 0 ) - last_tap_x_;
            float y = AMotionEvent_getY( motion_event, 0 ) - last_tap_y_;
            if( x * x + y * y < DOUBLE_TAP_SLOP * DOUBLE_TAP_SLOP * dp_factor_ )
            {
                LOGI( "DoubletapDetector: Doubletap detected" );
                return GESTURE_STATE_ACTION;
            }
        }
        break;
    }
    case AMOTION_EVENT_ACTION_UP:
        if( tap_detected )
        {
            last_tap_time_ = AMotionEvent_getEventTime( motion_event );
            last_tap_x_ = AMotionEvent_getX( motion_event, 0 );
            last_tap_y_ = AMotionEvent_getY( motion_event, 0 );
        }
        break;
    }
    return GESTURE_STATE_NONE;
}

void DoubletapDetector::SetConfiguration( AConfiguration* config )
{
    dp_factor_ = 160.f / AConfiguration_getDensity( config );
    tap_detector_.SetConfiguration( config );
}

//--------------------------------------------------------------------------------
// PinchDetector
//--------------------------------------------------------------------------------

int32_t PinchDetector::FindIndex( const AInputEvent* event, int32_t id )
{
    int32_t count = AMotionEvent_getPointerCount( event );
    for( int32_t i = 0; i < count; ++i )
    {
        if( id == AMotionEvent_getPointerId( event, i ) )
            return i;
    }
    return -1;
}

GESTURE_STATE PinchDetector::Detect( const AInputEvent* event )
{
    GESTURE_STATE ret = GESTURE_STATE_NONE;
    int32_t action = AMotionEvent_getAction( event );
    uint32_t flags = action & AMOTION_EVENT_ACTION_MASK;
    event_ = event;

    int32_t count = AMotionEvent_getPointerCount( event );
    switch( flags )
    {
    case AMOTION_EVENT_ACTION_DOWN:
        vec_pointers_.push_back( AMotionEvent_getPointerId( event, 0 ) );
        break;
    case AMOTION_EVENT_ACTION_POINTER_DOWN:
    {
        int32_t iIndex = (action & AMOTION_EVENT_ACTION_POINTER_INDEX_MASK)
                >> AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT;
        vec_pointers_.push_back( AMotionEvent_getPointerId( event, iIndex ) );
        if( count == 2 )
        {
            //Start new pinch
            ret = GESTURE_STATE_START;
        }
    }
        break;
    case AMOTION_EVENT_ACTION_UP:
        vec_pointers_.pop_back();
        break;
    case AMOTION_EVENT_ACTION_POINTER_UP:
    {
        int32_t index = (action & AMOTION_EVENT_ACTION_POINTER_INDEX_MASK)
                >> AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT;
        int32_t released_pointer_id = AMotionEvent_getPointerId( event, index );

        std::vector<int32_t>::iterator it = vec_pointers_.begin();
        std::vector<int32_t>::iterator it_end = vec_pointers_.end();
        int32_t i = 0;
        for( ; it != it_end; ++it, ++i )
        {
            if( *it == released_pointer_id )
            {
                vec_pointers_.erase( it );
                break;
            }
        }

        if( i <= 1 )
        {
            //Reset pinch or drag
            if( count != 2 )
            {
                //Start new pinch
                ret = GESTURE_STATE_START | GESTURE_STATE_END;
            }
        }
    }
        break;
    case AMOTION_EVENT_ACTION_MOVE:
        switch( count )
        {
        case 1:
            break;
        default:
            //Multi touch
            ret = GESTURE_STATE_MOVE;
            break;
        }
        break;
    case AMOTION_EVENT_ACTION_CANCEL:
        break;
    }

    return ret;
}

bool PinchDetector::GetPointers( Vec2& v1, Vec2& v2 )
{
    if( vec_pointers_.size() < 2 )
        return false;

    int32_t index = FindIndex( event_, vec_pointers_[0] );
    if( index == -1 )
        return false;

    float x = AMotionEvent_getX( event_, index );
    float y = AMotionEvent_getY( event_, index );

    index = FindIndex( event_, vec_pointers_[1] );
    if( index == -1 )
        return false;

    float x2 = AMotionEvent_getX( event_, index );
    float y2 = AMotionEvent_getY( event_, index );

    v1 = Vec2( x, y );
    v2 = Vec2( x2, y2 );

    return true;
}

//--------------------------------------------------------------------------------
// DragDetector
//--------------------------------------------------------------------------------

int32_t DragDetector::FindIndex( const AInputEvent* event, int32_t id )
{
    int32_t count = AMotionEvent_getPointerCount( event );
    for( int32_t i = 0; i < count; ++i )
    {
        if( id == AMotionEvent_getPointerId( event, i ) )
            return i;
    }
    return -1;
}

GESTURE_STATE DragDetector::Detect( const AInputEvent* event )
{
    GESTURE_STATE ret = GESTURE_STATE_NONE;
    int32_t action = AMotionEvent_getAction( event );
    int32_t index = (action & AMOTION_EVENT_ACTION_POINTER_INDEX_MASK)
            >> AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT;
    uint32_t flags = action & AMOTION_EVENT_ACTION_MASK;
    event_ = event;

    int32_t count = AMotionEvent_getPointerCount( event );
    switch( flags )
    {
    case AMOTION_EVENT_ACTION_DOWN:
        vec_pointers_.push_back( AMotionEvent_getPointerId( event, 0 ) );
        ret = GESTURE_STATE_START;
        break;
    case AMOTION_EVENT_ACTION_POINTER_DOWN:
        vec_pointers_.push_back( AMotionEvent_getPointerId( event, index ) );
        break;
    case AMOTION_EVENT_ACTION_UP:
        vec_pointers_.pop_back();
        ret = GESTURE_STATE_END;
        break;
    case AMOTION_EVENT_ACTION_POINTER_UP:
    {
        int32_t released_pointer_id = AMotionEvent_getPointerId( event, index );

        std::vector<int32_t>::iterator it = vec_pointers_.begin();
        std::vector<int32_t>::iterator it_end = vec_pointers_.end();
        int32_t i = 0;
        for( ; it != it_end; ++it, ++i )
        {
            if( *it == released_pointer_id )
            {
                vec_pointers_.erase( it );
                break;
            }
        }

        if( i <= 1 )
        {
            //Reset pinch or drag
            if( count == 2 )
            {
                ret = GESTURE_STATE_START;
            }
        }
        break;
    }
    case AMOTION_EVENT_ACTION_MOVE:
        switch( count )
        {
        case 1:
            //Drag
            ret = GESTURE_STATE_MOVE;
            break;
        default:
            break;
        }
        break;
    case AMOTION_EVENT_ACTION_CANCEL:
        break;
    }

    return ret;
}

bool DragDetector::GetPointer( Vec2& v )
{
    if( vec_pointers_.size() < 1 )
        return false;

    int32_t iIndex = FindIndex( event_, vec_pointers_[0] );
    if( iIndex == -1 )
        return false;

    float x = AMotionEvent_getX( event_, iIndex );
    float y = AMotionEvent_getY( event_, iIndex );

    v = Vec2( x, y );

    return true;
}

}   //namespace ndkHelper

