/*
 * Copyright (C) 2017 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
 */

package com.android.systemui.statusbar.phone;

import android.view.MotionEvent;
import android.view.View;
import android.view.ViewConfiguration;

import com.android.systemui.R;

/**
 * Detects a double tap.
 */
public class DoubleTapHelper {

    private static final long DOUBLETAP_TIMEOUT_MS = 1200;

    private final View mView;
    private final ActivationListener mActivationListener;
    private final DoubleTapListener mDoubleTapListener;
    private final SlideBackListener mSlideBackListener;
    private final DoubleTapLogListener mDoubleTapLogListener;

    private float mTouchSlop;
    private float mDoubleTapSlop;

    private boolean mActivated;

    private float mDownX;
    private float mDownY;
    private boolean mTrackTouch;

    private float mActivationX;
    private float mActivationY;
    private Runnable mTapTimeoutRunnable = this::makeInactive;

    public DoubleTapHelper(View view, ActivationListener activationListener,
            DoubleTapListener doubleTapListener, SlideBackListener slideBackListener,
            DoubleTapLogListener doubleTapLogListener) {
        mTouchSlop = ViewConfiguration.get(view.getContext()).getScaledTouchSlop();
        mDoubleTapSlop = view.getResources().getDimension(R.dimen.double_tap_slop);
        mView = view;

        mActivationListener = activationListener;
        mDoubleTapListener = doubleTapListener;
        mSlideBackListener = slideBackListener;
        mDoubleTapLogListener = doubleTapLogListener;
    }

    public boolean onTouchEvent(MotionEvent event) {
        return onTouchEvent(event, Integer.MAX_VALUE);
    }

    public boolean onTouchEvent(MotionEvent event, int maxTouchableHeight) {
        int action = event.getActionMasked();
        switch (action) {
            case MotionEvent.ACTION_DOWN:
                mDownX = event.getX();
                mDownY = event.getY();
                mTrackTouch = true;
                if (mDownY > maxTouchableHeight) {
                    mTrackTouch = false;
                }
                break;
            case MotionEvent.ACTION_MOVE:
                if (!isWithinTouchSlop(event)) {
                    makeInactive();
                    mTrackTouch = false;
                }
                break;
            case MotionEvent.ACTION_UP:
                if (isWithinTouchSlop(event)) {
                    if (mSlideBackListener != null && mSlideBackListener.onSlideBack()) {
                        return true;
                    }
                    if (!mActivated) {
                        makeActive();
                        mView.postDelayed(mTapTimeoutRunnable, DOUBLETAP_TIMEOUT_MS);
                        mActivationX = event.getX();
                        mActivationY = event.getY();
                    } else {
                        boolean withinDoubleTapSlop = isWithinDoubleTapSlop(event);
                        if (mDoubleTapLogListener != null) {
                            mDoubleTapLogListener.onDoubleTapLog(withinDoubleTapSlop,
                                    event.getX() - mActivationX,
                                    event.getY() - mActivationY);
                        }
                        if (withinDoubleTapSlop) {
                            if (!mDoubleTapListener.onDoubleTap()) {
                                return false;
                            }
                        } else {
                            makeInactive();
                            mTrackTouch = false;
                        }
                    }
                } else {
                    makeInactive();
                    mTrackTouch = false;
                }
                break;
            case MotionEvent.ACTION_CANCEL:
                makeInactive();
                mTrackTouch = false;
                break;
            default:
                break;
        }
        return mTrackTouch;
    }

    private void makeActive() {
        if (!mActivated) {
            mActivated = true;
            mActivationListener.onActiveChanged(true);
        }
    }

    private void makeInactive() {
        if (mActivated) {
            mActivated = false;
            mActivationListener.onActiveChanged(false);
        }
    }

    private boolean isWithinTouchSlop(MotionEvent event) {
        return Math.abs(event.getX() - mDownX) < mTouchSlop
                && Math.abs(event.getY() - mDownY) < mTouchSlop;
    }

    private boolean isWithinDoubleTapSlop(MotionEvent event) {
        if (!mActivated) {
            // If we're not activated there's no double tap slop to satisfy.
            return true;
        }

        return Math.abs(event.getX() - mActivationX) < mDoubleTapSlop
                && Math.abs(event.getY() - mActivationY) < mDoubleTapSlop;
    }

    @FunctionalInterface
    public interface ActivationListener {
        void onActiveChanged(boolean active);
    }

    @FunctionalInterface
    public interface DoubleTapListener {
        boolean onDoubleTap();
    }

    @FunctionalInterface
    public interface SlideBackListener {
        boolean onSlideBack();
    }

    @FunctionalInterface
    public interface DoubleTapLogListener {
        void onDoubleTapLog(boolean accepted, float dx, float dy);
    }
}
