package com.marozzi.roundbutton.animations;

import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.AnimatorSet;
import android.animation.ValueAnimator;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.ColorFilter;
import android.graphics.Paint;
import android.graphics.PixelFormat;
import android.graphics.PorterDuff;
import android.graphics.PorterDuffColorFilter;
import android.graphics.Rect;
import android.graphics.RectF;
import android.support.annotation.ColorInt;
import android.support.annotation.DrawableRes;
import android.support.annotation.NonNull;
import android.support.v4.content.ContextCompat;
import android.view.View;
import android.view.animation.AccelerateDecelerateInterpolator;
import android.view.animation.Interpolator;
import android.view.animation.LinearInterpolator;

import com.marozzi.roundbutton.RoundButtonHelper;

public class CircularAnimatedDrawable extends BaseAnimatedDrawable {

    private ValueAnimator mValueAnimatorAngle;
    private ValueAnimator mValueAnimatorSweep;
    private AnimatorSet mAnimatorSet;
    private static final Interpolator ANGLE_INTERPOLATOR = new LinearInterpolator();
    private static final Interpolator SWEEP_INTERPOLATOR = new AccelerateDecelerateInterpolator();
    private static final int ANGLE_ANIMATOR_DURATION = 2000;
    private static final int SWEEP_ANIMATOR_DURATION = 700;
    private static final Float MIN_SWEEP_ANGLE = 50f;

    private final RectF fBounds = new RectF();
    private Paint mPaint;
    private View mAnimatedView;

    private float mBorderWidth;
    private float mCurrentGlobalAngle;
    private float mCurrentSweepAngle;
    private float mCurrentGlobalAngleOffset;

    private boolean mModeAppearing;
    private boolean mRunning;

    private boolean shouldDraw;

    private RectF innerBounds = new RectF();
    private Paint innerPaint;
    private Bitmap innerImage;

    /**
     * @param view        View to be animated
     * @param borderWidth The width of the spinning bar
     * @param arcColor    The color of the spinning bar
     */
    public CircularAnimatedDrawable(View view, float borderWidth, int arcColor) {
        this(view, borderWidth, arcColor, 0, Color.BLACK);
    }

    /**
     * @param view        View to be animated
     * @param borderWidth The width of the spinning bar
     * @param arcColor    The color of the spinning bar
     */
    public CircularAnimatedDrawable(View view, float borderWidth, int arcColor, @DrawableRes int innerResource, @ColorInt int innerResourceColorFilter) {
        mAnimatedView = view;
        mBorderWidth = borderWidth;

        if (innerResource != 0) {
            setInnerResource(innerResource);
            setInnerResourceColorFilter(innerResourceColorFilter);
        }

        mPaint = new Paint();
        mPaint.setAntiAlias(true);
        mPaint.setStyle(Paint.Style.STROKE);
        mPaint.setStrokeWidth(borderWidth);
        mPaint.setColor(arcColor);

        setupAnimations();

        shouldDraw = true;

        mAnimatorSet = new AnimatorSet();
    }

    public void setInnerResource(@DrawableRes int innerResource) {
        innerImage = RoundButtonHelper.getBitmap(ContextCompat.getDrawable(mAnimatedView.getContext(), innerResource));
    }

    public void setInnerResourceColorFilter(@ColorInt int resourceColorFilter) {
        innerPaint = new Paint();
        innerPaint.setAntiAlias(true);
        innerPaint.setColorFilter(new PorterDuffColorFilter(resourceColorFilter, PorterDuff.Mode.SRC_IN));
    }

    @Override
    protected void onBoundsChange(Rect bounds) {
        super.onBoundsChange(bounds);
        fBounds.left = bounds.left + mBorderWidth / 2f + .5f;
        fBounds.right = bounds.right - mBorderWidth / 2f - .5f;
        fBounds.top = bounds.top + mBorderWidth / 2f + .5f;
        fBounds.bottom = bounds.bottom - mBorderWidth / 2f - .5f;

        innerBounds.left = fBounds.left + 2.5f;
        innerBounds.right = fBounds.right - 2.5f;
        innerBounds.top = fBounds.top + 2.5f;
        innerBounds.bottom = fBounds.bottom - 2.5f;

        if (innerImage != null) {
            int bitMapWidth = (int) (innerBounds.right - innerBounds.left);
            int bitMapHeight = (int) (innerBounds.bottom - innerBounds.top);
            innerImage = Bitmap.createScaledBitmap(innerImage, bitMapWidth, bitMapHeight, false);
        }
    }

    /**
     * Start the animation
     */
    @Override
    public void start() {
        if (isRunning()) {
            return;
        }

        mRunning = true;

        mAnimatorSet.playTogether(mValueAnimatorAngle, mValueAnimatorSweep);
        mAnimatorSet.start();
    }

    /**
     * Stops the animation
     */
    @Override
    public void stop() {
        if (!isRunning()) {
            return;
        }

        mRunning = false;
        mAnimatorSet.cancel();
    }

    /**
     * Method the inform if the animation is in process
     *
     * @return
     */
    @Override
    public boolean isRunning() {
        return mRunning;
    }

    /**
     * Method called when the drawable is going to draw itself.
     *
     * @param canvas
     */
    @Override
    public void draw(@NonNull Canvas canvas) {
        float startAngle = mCurrentGlobalAngle - mCurrentGlobalAngleOffset;
        float sweepAngle = mCurrentSweepAngle;

        if (!mModeAppearing) {
            startAngle = startAngle + sweepAngle;
            sweepAngle = 360 - sweepAngle - MIN_SWEEP_ANGLE;
        } else {
            sweepAngle += MIN_SWEEP_ANGLE;
        }

        canvas.drawArc(fBounds, startAngle, sweepAngle, false, mPaint);

        if (innerImage != null)
            canvas.drawBitmap(innerImage, null, innerBounds, innerPaint);
    }

    @Override
    public void setAlpha(int alpha) {
        mPaint.setAlpha(alpha);
    }

    @Override
    public void setColorFilter(ColorFilter colorFilter) {
        mPaint.setColorFilter(colorFilter);
    }

    @Override
    public int getOpacity() {
        return PixelFormat.TRANSPARENT;
    }

    @Override
    public void setupAnimations() {
        mValueAnimatorAngle = ValueAnimator.ofFloat(0, 360f);
        mValueAnimatorAngle.setInterpolator(ANGLE_INTERPOLATOR);
        mValueAnimatorAngle.setDuration(ANGLE_ANIMATOR_DURATION);
        mValueAnimatorAngle.setRepeatCount(ValueAnimator.INFINITE);
        mValueAnimatorAngle.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                mCurrentGlobalAngle = (float) animation.getAnimatedValue();
            }
        });

        mValueAnimatorSweep = ValueAnimator.ofFloat(0, 360f - 2 * MIN_SWEEP_ANGLE);
        mValueAnimatorSweep.setInterpolator(SWEEP_INTERPOLATOR);
        mValueAnimatorSweep.setDuration(SWEEP_ANIMATOR_DURATION);
        mValueAnimatorSweep.setRepeatCount(ValueAnimator.INFINITE);
        mValueAnimatorSweep.addListener(new AnimatorListenerAdapter() {
            @Override
            public void onAnimationRepeat(Animator animation) {
                toggleAppearingMode();
                shouldDraw = false;
            }
        });

        mValueAnimatorSweep.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                mCurrentSweepAngle = (float) animation.getAnimatedValue();

                if (mCurrentSweepAngle < 5) {
                    shouldDraw = true;
                }

                if (shouldDraw) {
                    mAnimatedView.invalidate();
                }
            }
        });

    }

    /**
     * This method is called in every repetition of the animation, so the animation make the sweep
     * growing and then make it shirinking.
     */
    private void toggleAppearingMode() {
        mModeAppearing = !mModeAppearing;

        if (mModeAppearing) {
            mCurrentGlobalAngleOffset = (mCurrentGlobalAngleOffset + MIN_SWEEP_ANGLE * 2) % 360;
        }
    }

    @Override
    public void dispose() {
        if (mValueAnimatorAngle != null) {
            mValueAnimatorAngle.end();
            mValueAnimatorAngle.removeAllUpdateListeners();
            mValueAnimatorAngle.cancel();
        }

        mValueAnimatorAngle = null;

        if (mValueAnimatorSweep != null) {
            mValueAnimatorSweep.end();
            mValueAnimatorSweep.removeAllUpdateListeners();
            mValueAnimatorSweep.cancel();
        }

        mValueAnimatorSweep = null;

        if (mAnimatorSet != null) {
            mAnimatorSet.end();
            mAnimatorSet.cancel();
        }
    }
}