package mbanje.kurt.fabbutton;

import android.animation.AnimatorSet;
import android.animation.ValueAnimator;
import android.view.View;
import android.view.animation.DecelerateInterpolator;
import android.view.animation.LinearInterpolator;

public class FabUtil {
    public static final float INDETERMINANT_MIN_SWEEP = 15f;
    public static final int ANIMATION_STEPS = 4;

    /**
     * the animation callback interface that should be used by classes that want to listen for events from the library animations
     */
    public interface OnFabValueCallback {
        void onIndeterminateValuesChanged(float indeterminateSweep, float indeterminateRotateOffset, float startAngle, float progress);
    }

    /**
     * Creates the starting angle animator for the circleview
     *
     * @param view the view that the animator is to be attached too
     * @param from animate from value
     * @param to animate to value
     * @param callback the callback interface for animations
     * @return ValueAnimator instance
     */
    public static ValueAnimator createStartAngleAnimator(final View view, float from, float to, final OnFabValueCallback callback) {
        ValueAnimator animator = ValueAnimator.ofFloat(from, to);
        animator.setDuration(5000);
        animator.setInterpolator(new DecelerateInterpolator(2));
        animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                float startAngle = (Float) animation.getAnimatedValue();
                callback.onIndeterminateValuesChanged(-1, -1, startAngle, -1);
                view.invalidate();
            }
        });
        return animator;
    }


    /**
     * Creates a progress animator
     *
     * @param view the view that the animator is to be attached too
     * @param from animate from value
     * @param to animate to value
     * @param callback the callback interface for animations
     * @return ValueAnimator instance
     */
    public static ValueAnimator createProgressAnimator(final View view, float from, float to, final OnFabValueCallback callback, int duration) {
        ValueAnimator animator = ValueAnimator.ofFloat(from, to);
        animator.setDuration(duration);
        animator.setInterpolator(new LinearInterpolator());
        animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                float actualProgress = (Float) animation.getAnimatedValue();
                callback.onIndeterminateValuesChanged(-1, -1, -1, actualProgress);
                view.invalidate();
            }
        });
        return animator;
    }


    /**
     * Creates a progress animator
     *
     * @param view the view that the animator is to be attached too
     * @param step animation steps of the prgress animation
     * @param animDuration duration of the animation i.e. 1 cycle
     * @param callback the callback interface for animations
     * @return AnimatorSet instance
     */
    public static AnimatorSet createIndeterminateAnimator(final View view, float step, int animDuration, final OnFabValueCallback callback) {
        final float maxSweep = 360f * (ANIMATION_STEPS - 1) / ANIMATION_STEPS + INDETERMINANT_MIN_SWEEP;
        final float start = -90f + step * (maxSweep - INDETERMINANT_MIN_SWEEP);
        // Extending the front of the arc
        ValueAnimator frontEndExtend = ValueAnimator.ofFloat(INDETERMINANT_MIN_SWEEP, maxSweep);
        frontEndExtend.setDuration(animDuration / ANIMATION_STEPS / 2);
        frontEndExtend.setInterpolator(new DecelerateInterpolator(1));
        frontEndExtend.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                float indeterminateSweep = (Float) animation.getAnimatedValue();
                callback.onIndeterminateValuesChanged(indeterminateSweep, -1, -1, -1);
                view.invalidate();
            }
        });

        // Overall rotation
        ValueAnimator rotateAnimator1 = ValueAnimator.ofFloat(step * 720f / ANIMATION_STEPS, (step + .5f) * 720f / ANIMATION_STEPS);
        rotateAnimator1.setDuration(animDuration / ANIMATION_STEPS / 2);
        rotateAnimator1.setInterpolator(new LinearInterpolator());
        rotateAnimator1.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                float indeterminateRotateOffset = (Float) animation.getAnimatedValue();
                callback.onIndeterminateValuesChanged(-1, indeterminateRotateOffset, -1, -1);
            }
        });

        // Retracting the back end of the arc
        ValueAnimator backEndRetract = ValueAnimator.ofFloat(start, start + maxSweep - INDETERMINANT_MIN_SWEEP);
        backEndRetract.setDuration(animDuration / ANIMATION_STEPS / 2);
        backEndRetract.setInterpolator(new DecelerateInterpolator(1));
        backEndRetract.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                float startAngle = (Float) animation.getAnimatedValue();
                float indeterminateSweep = maxSweep - startAngle + start;
                callback.onIndeterminateValuesChanged(indeterminateSweep, -1, startAngle, -1);
                view.invalidate();
            }
        });

        // More overall rotation
        ValueAnimator rotateAnimator2 = ValueAnimator.ofFloat((step + .5f) * 720f / ANIMATION_STEPS, (step + 1) * 720f / ANIMATION_STEPS);
        rotateAnimator2.setDuration(animDuration / ANIMATION_STEPS / 2);
        rotateAnimator2.setInterpolator(new LinearInterpolator());
        rotateAnimator2.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                float indeterminateRotateOffset = (Float) animation.getAnimatedValue();
                callback.onIndeterminateValuesChanged(-1, indeterminateRotateOffset, -1, -1);
            }
        });

        AnimatorSet set = new AnimatorSet();
        set.play(frontEndExtend).with(rotateAnimator1);
        set.play(backEndRetract).with(rotateAnimator2).after(rotateAnimator1);
        return set;
    }
}
