/*
 * 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 org.oscim.utils.animation;

/**
 * See https://developer.android.com/reference/android/support/animation/FlingAnimation.html
 * Class androidx.dynamicanimation.animation.FlingAnimation
 */
public final class DragForce {

    private static final float DEFAULT_FRICTION = -4.2f;
    private static final float DEFAULT_MIN_VISIBLE_CHANGE_PIXELS = 0.5f;

    // This multiplier is used to calculate the velocity threshold given a certain value
    // threshold. The idea is that if it takes >= 1 frame to move the value threshold amount,
    // then the velocity is a reasonable threshold.
    private static final float VELOCITY_THRESHOLD_MULTIPLIER = 1000f / 16f; // 1 frame ≙ 16 ms (62.5 fps)
    private float mFriction = DEFAULT_FRICTION;
    private float mVelocityThreshold = DEFAULT_MIN_VISIBLE_CHANGE_PIXELS * VELOCITY_THRESHOLD_MULTIPLIER;

    // Internal state to hold a value/velocity pair.
    private float mValue;
    private float mVelocity;

    public void setFrictionScalar(float frictionScalar) {
        mFriction = frictionScalar * DEFAULT_FRICTION;
    }

    public float getFrictionScalar() {
        return mFriction / DEFAULT_FRICTION;
    }

    /**
     * Updates the animation state (i.e. value and velocity).
     *
     * @param deltaT time elapsed in millisecond since last frame
     * @return the value delta since last frame
     */
    public float updateValueAndVelocity(long deltaT) {
        float velocity = mVelocity;
        mVelocity = (float) (velocity * Math.exp((deltaT / 1000f) * mFriction));
        float valueDelta = (mVelocity - velocity);
        mValue += valueDelta;
        if (isAtEquilibrium(mValue, mVelocity)) {
            mVelocity = 0f;
        }
        return valueDelta;
    }

    public void setValueAndVelocity(float value, float velocity) {
        mValue = value;
        mVelocity = velocity;
    }

    public float getValue() {
        return mValue;
    }

    public float getVelocity() {
        return mVelocity;
    }

    public float getAcceleration(float position, float velocity) {
        return velocity * mFriction;
    }

    public boolean isAtEquilibrium(float value, float velocity) {
        return Math.abs(velocity) < mVelocityThreshold;
    }

    public void setValueThreshold(float threshold) {
        mVelocityThreshold = threshold * VELOCITY_THRESHOLD_MULTIPLIER;
    }
}
