// Copyright 2013 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

package org.chromium.content.browser;

import android.content.Context;
import android.util.DisplayMetrics;
import android.view.MotionEvent;

/**
 * This objects controls the scroll snapping behavior based on scroll updates.
 */
class SnapScrollController {
    private static final String TAG = "SnapScrollController";
    private static final int SNAP_NONE = 0;
    private static final int SNAP_HORIZ = 1;
    private static final int SNAP_VERT = 2;
    private static final int SNAP_BOUND = 16;

    private float mChannelDistance = 16f;
    private int mSnapScrollMode = SNAP_NONE;
    private int mFirstTouchX = -1;
    private int mFirstTouchY = -1;
    private float mDistanceX = 0;
    private float mDistanceY = 0;

    SnapScrollController(Context context) {
        calculateChannelDistance(context);
    }

    /**
     * Updates the snap scroll mode based on the given X and Y distance to be moved on scroll.
     * If the scroll update is above a threshold, the snapping behavior is reset.
     * @param distanceX X distance for the current scroll update.
     * @param distanceY Y distance for the current scroll update.
     */
    void updateSnapScrollMode(float distanceX, float distanceY) {
        if (mSnapScrollMode == SNAP_HORIZ || mSnapScrollMode == SNAP_VERT) {
            mDistanceX += Math.abs(distanceX);
            mDistanceY += Math.abs(distanceY);
            if (mSnapScrollMode == SNAP_HORIZ) {
                if (mDistanceY > mChannelDistance) {
                    mSnapScrollMode = SNAP_NONE;
                } else if (mDistanceX > mChannelDistance) {
                    mDistanceX = 0;
                    mDistanceY = 0;
                }
            } else {
                if (mDistanceX > mChannelDistance) {
                    mSnapScrollMode = SNAP_NONE;
                } else if (mDistanceY > mChannelDistance) {
                    mDistanceX = 0;
                    mDistanceY = 0;
                }
            }
        }
    }

    /**
     * Sets the snap scroll mode based on the event type.
     * @param event The received MotionEvent.
     */
    void setSnapScrollingMode(MotionEvent event, boolean isScaleGestureDetectionInProgress) {
        switch(event.getAction()) {
            case MotionEvent.ACTION_DOWN:
                mSnapScrollMode = SNAP_NONE;
                mFirstTouchX = (int) event.getX();
                mFirstTouchY = (int) event.getY();
                break;
             // Set scrolling mode to SNAP_X if scroll towards x-axis exceeds SNAP_BOUND
             // and movement towards y-axis is trivial.
             // Set scrolling mode to SNAP_Y if scroll towards y-axis exceeds SNAP_BOUND
             // and movement towards x-axis is trivial.
             // Scrolling mode will remain in SNAP_NONE for other conditions.
            case MotionEvent.ACTION_MOVE:
                if (!isScaleGestureDetectionInProgress &&
                        mSnapScrollMode == SNAP_NONE) {
                    int xDiff = (int) Math.abs(event.getX() - mFirstTouchX);
                    int yDiff = (int) Math.abs(event.getY() - mFirstTouchY);
                    if (xDiff > SNAP_BOUND && yDiff < SNAP_BOUND) {
                        mSnapScrollMode = SNAP_HORIZ;
                    } else if (xDiff < SNAP_BOUND && yDiff > SNAP_BOUND) {
                        mSnapScrollMode = SNAP_VERT;
                    }
                }
                break;
            case MotionEvent.ACTION_UP:
            case MotionEvent.ACTION_CANCEL:
                mFirstTouchX = -1;
                mFirstTouchY = -1;
                mDistanceX = 0;
                mDistanceY = 0;
                break;
            default:
                break;
        }
    }

    private void calculateChannelDistance(Context context) {
        // The channel distance is adjusted for density and screen size.
        final DisplayMetrics metrics = context.getResources().getDisplayMetrics();
        final double screenSize = Math.hypot((double) metrics.widthPixels / metrics.densityDpi,
                (double) metrics.heightPixels / metrics.densityDpi);
        if (screenSize < 3.0) {
            mChannelDistance = 16f;
        } else if (screenSize < 5.0) {
            mChannelDistance = 22f;
        } else if (screenSize < 7.0) {
            mChannelDistance = 28f;
        } else {
            mChannelDistance = 34f;
        }
        mChannelDistance = mChannelDistance * metrics.density;
        if (mChannelDistance < 16f) mChannelDistance = 16f;
    }

    /**
     * @return Whether current snap scroll mode is vertical.
     */
    boolean isSnapVertical() {
        return mSnapScrollMode == SNAP_VERT;
    }

    /**
     * @return Whether current snap scroll mode is horizontal.
     */
    boolean isSnapHorizontal() {
        return mSnapScrollMode == SNAP_HORIZ;
    }

    /**
     * @return Whether currently snapping scrolls.
     */
    boolean isSnappingScrolls() {
        return mSnapScrollMode != SNAP_NONE;
    }
}
