/*
 * Copyright (C) 2021 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 androidx.constraintlayout.core.motion.utils;

/**
 * This contains the class to provide the logic for an animation to come to a stop using a spring
 * model.
 *
 * @hide
 */
public class SpringStopEngine implements StopEngine {
    double mDamping = 0.5f;
    private static final double UNSET = Double.MAX_VALUE;
    private boolean mInitialized = false;
    private double mStiffness;
    private double mTargetPos;
    private double mLastVelocity;
    private float mLastTime;
    private float mPos;
    private float mV;
    private float mMass;
    private float mStopThreshold;
    private int mBoundaryMode = 0;

    @Override
    public String debug(String desc, float time) {
        return null;
    }

    void log(String str) {
        StackTraceElement s = new Throwable().getStackTrace()[1];
        String line = ".(" + s.getFileName() + ":" + s.getLineNumber() + ") " + s.getMethodName() + "() ";
        System.out.println(line + str);
    }

    public void springConfig(float currentPos, float target, float currentVelocity, float mass,
                             float stiffness, float damping, float stopThreshold, int boundaryMode) {
        mTargetPos = target;
        mDamping = damping;
        mInitialized = false;
        mPos = currentPos;
        mLastVelocity = currentVelocity;
        mStiffness = stiffness;
        mMass = mass;
        mStopThreshold = stopThreshold;
        mBoundaryMode = boundaryMode;
        mLastTime = 0;
    }

    @Override
    public float getVelocity(float t) {
        return (float) mV;
    }

    @Override
    public float getInterpolation(float time) {
        compute(time - mLastTime);
        mLastTime = time;
        return (float) (mPos);
    }

    public float getAcceleration() {
        double k = mStiffness;
        double c = mDamping;
        double x = (mPos - mTargetPos);
        return (float) (-k * x - c * mV) / mMass;
    }

    @Override
    public float getVelocity() {
        return 0;
    }

    @Override
    public boolean isStopped() {
        double x = (mPos - mTargetPos);
        double k = mStiffness;
        double v = mV;
        double m = mMass;
        double energy = v * v * m + k * x * x;
        double max_def = Math.sqrt(energy / k);
        return max_def <= mStopThreshold;
    }

    private void compute(double dt) {
        double x = (mPos - mTargetPos);
        double k = mStiffness;
        double c = mDamping;
        double a = (-k * x - c * mV) / mMass;
        // This refinement of a simple coding of the acceleration increases accuracy
        double avgV = mV + a * dt / 2; // pass 1 calculate the average velocity
        double avgX = x + dt * (avgV) / 2 - mTargetPos;// pass 1 calculate the average pos
        a = (-avgX * k - avgV * c) / mMass; //  calculate acceleration over that average pos

        double dv = a * dt; //  calculate change in velocity
        avgV = mV + dv / 2; //  average  velocity is current + half change
        mV += dv;
        mPos += avgV * dt;
        if (mBoundaryMode > 0) {
            if (mPos < 0 && ((mBoundaryMode & 1) == 1)) {
                mPos = -mPos;
                mV = -mV;
            }
            if (mPos > 1 && ((mBoundaryMode & 2) == 2)) {
                mPos = 2 - mPos;
                mV = -mV;
            }
        }
    }
    
}

@@ -2967,47 +2967,8 @@
 ) %7B%0A
-        double x = (mPos - mTargetPos);
 %0A   
@@ -3016,32 +3016,337 @@
 e c = mDamping;%0A
+        // Estimate how many time we should over sample based on the frequency and current sampling%0A        int overSample = (int) (1 + 9 / (Math.sqrt(mStiffness / mMass) * dt * 4));%0A        dt /= overSample;%0A%0A        for (int i = 0; i %3C overSample; i++) %7B%0A            double x = (mPos - mTargetPos);%0A    
         double a
@@ -3367,32 +3367,36 @@
  * mV) / mMass;%0A
+    
         // This 
@@ -3464,32 +3464,36 @@
 ccuracy%0A        
+    
 double avgV = mV
@@ -3548,32 +3548,36 @@
 elocity%0A        
+  
+  
 double avgX = x 
@@ -3643,16 +3643,20 @@
 age pos%0A
+    
         
@@ -3745,24 +3745,28 @@
 os%0A%0A        
+    
 double dv = 
@@ -3814,16 +3814,20 @@
         
+  
+  
 avgV = m
@@ -3893,16 +3893,20 @@
         
+    
 mV += dv
@@ -3907,16 +3907,20 @@
  += dv;%0A
+    
         
@@ -3942,24 +3942,28 @@
 dt;%0A        
+  
+  
 if (mBoundar
@@ -3971,24 +3971,28 @@
 Mode %3E 0) %7B%0A
+    
             
@@ -4049,24 +4049,28 @@
             
+    
 mPos = -mPos
@@ -4079,32 +4079,36 @@
                 
+  
+  
 mV = -mV;%0A      
@@ -4105,34 +4105,42 @@
 mV;%0A            
-%7D%0A
+    %7D%0A    
             if (
@@ -4193,24 +4193,28 @@
             
+  
+  
 mPos = 2 - m
@@ -4226,32 +4226,36 @@
                 
+    
 mV = -mV;%0A      
@@ -4240,32 +4240,50 @@
       mV = -mV;%0A
+                %7D%0A
             %7D%0A  

fix undersampling of curve at high frequency (#207)