package net.derohimat.baseapp.ui.view;

import android.graphics.Canvas;
import android.graphics.ColorFilter;
import android.graphics.Paint;
import android.graphics.drawable.Drawable;
import android.os.Build;
import android.view.MotionEvent;
import android.view.View;

import com.nineoldandroids.animation.Animator;
import com.nineoldandroids.animation.AnimatorListenerAdapter;
import com.nineoldandroids.animation.ObjectAnimator;
import com.nineoldandroids.util.FloatProperty;
import com.nineoldandroids.util.Property;

public class BaseRippleDrawable extends Drawable implements View.OnTouchListener {

    final static int DEFAULT_ANIM_DURATION = 250;
    final static float END_RIPPLE_TOUCH_RADIUS = 150f;
    final static float END_SCALE = 1.3f;
    final static int RIPPLE_TOUCH_MIN_ALPHA = 40;
    final static int RIPPLE_TOUCH_MAX_ALPHA = 120;
    final static Property<BaseRippleDrawable, Float> CREATE_TOUCH_RIPPLE =
            new FloatProperty<BaseRippleDrawable>("createTouchRipple") {
                @Override
                public void setValue(BaseRippleDrawable object, float value) {
                    object.createTouchRipple(value);
                }

                @Override
                public Float get(BaseRippleDrawable object) {
                    return object.getAnimationState();
                }
            };
    final static int RIPPLE_BACKGROUND_ALPHA = 100;
    final static Property<BaseRippleDrawable, Float> DESTROY_TOUCH_RIPPLE =
            new FloatProperty<BaseRippleDrawable>("destroyTouchRipple") {
                @Override
                public void setValue(BaseRippleDrawable object, float value) {
                    object.destroyTouchRipple(value);
                }

                @Override
                public Float get(BaseRippleDrawable object) {
                    return object.getAnimationState();
                }
            };
    Paint mRipplePaint = new Paint(Paint.ANTI_ALIAS_FLAG);
    Paint mRippleBackgroundPaint = new Paint(Paint.ANTI_ALIAS_FLAG);

    Circle mTouchRipple;
    Circle mBackgroundRipple;

    ObjectAnimator mCurrentAnimator;

    Drawable mOriginalBackground;
    int mViewSize = 0;
    float mAnimationValue;

    public BaseRippleDrawable() {
        initRippleElements();
    }

    /**
     * Creates ripple effect to mView
     *
     * @param v            mView to apply ripple drawable
     * @param primaryColor color of ripples
     */
    public static void createRipple(View v, int primaryColor) {
        BaseRippleDrawable rippleDrawable = new BaseRippleDrawable();
        rippleDrawable.setDrawable(v.getBackground());
        rippleDrawable.setColor(primaryColor);
        rippleDrawable.setBounds(v.getPaddingLeft(), v.getPaddingTop(),
                v.getPaddingRight(), v.getPaddingBottom());

        v.setOnTouchListener(rippleDrawable);
        if (Build.VERSION.SDK_INT >= 16) {
            v.setBackground(rippleDrawable);
        } else {
            //noinspection deprecation
            v.setBackgroundDrawable(rippleDrawable);
        }
    }

    /**
     * Creates ripple animation to mView, difference between {@code createRipple(v, primaryColor)}
     * here animation will start automatically without
     * user touch event
     *
     * @param x            fake touch coordinate x
     * @param y            fake touch coordinate y
     * @param v            mView to apply ripple drawable
     * @param primaryColor ripple animation color
     */
    public static void createRipple(int x, int y, View v, int primaryColor) {
        if (!(v.getBackground() instanceof BaseRippleDrawable)) {
            createRipple(v, primaryColor);
        }
        BaseRippleDrawable drawable = (BaseRippleDrawable) v.getBackground();
        drawable.setColor(primaryColor);
        drawable.onFingerDown(v, x, y);
    }

    /**
     * Set colors of ripples
     *
     * @param primaryColor color of ripples
     */
    public void setColor(int primaryColor) {
        mRippleBackgroundPaint.setColor(primaryColor);
        mRippleBackgroundPaint.setAlpha(RIPPLE_BACKGROUND_ALPHA);
        mRipplePaint.setColor(primaryColor);

        invalidateSelf();
    }

    /**
     * set first layer you background drawable
     *
     * @param drawable original background
     */
    public void setDrawable(Drawable drawable) {
        mOriginalBackground = drawable;

        invalidateSelf();
    }

    void initRippleElements() {
        mTouchRipple = new Circle();
        mBackgroundRipple = new Circle();

        mRipplePaint.setStyle(Paint.Style.FILL);
        mRippleBackgroundPaint.setStyle(Paint.Style.FILL);
    }

    @Override
    public void draw(Canvas canvas) {
        if (mOriginalBackground != null) {
            mOriginalBackground.setBounds(getBounds());
            mOriginalBackground.draw(canvas);
        }

        mBackgroundRipple.draw(canvas, mRippleBackgroundPaint);
        mTouchRipple.draw(canvas, mRipplePaint);
    }

    @Override
    public void setAlpha(int alpha) {
    }

    @Override
    public void setColorFilter(ColorFilter cf) {
    }

    @Override
    public int getOpacity() {
        return 0;
    }

    @Override
    public boolean onTouch(View v, MotionEvent event) {
        final int action = event.getAction();
        switch (action) {
            case MotionEvent.ACTION_DOWN:
                onFingerDown(v, event.getX(), event.getY());
                v.onTouchEvent(event); // Fix for views, to handle clicks
                return true; // fix for scroll, when ACTION_UP & ACTION_CANCEL not come

            case MotionEvent.ACTION_MOVE:
                onFingerMove(event.getX(), event.getY());
                break;
            case MotionEvent.ACTION_UP:
            case MotionEvent.ACTION_CANCEL:
                onFingerUp();
                break;
        }
        return false;
    }

    void onFingerDown(View v, float x, float y) {
        mTouchRipple.cx = mBackgroundRipple.cx = x;
        mTouchRipple.cy = mBackgroundRipple.cy = y;
        mTouchRipple.radius = mBackgroundRipple.radius = 0f;
        mViewSize = Math.max(v.getWidth(), v.getHeight());

        if (mCurrentAnimator == null) {
            mRippleBackgroundPaint.setAlpha(RIPPLE_BACKGROUND_ALPHA);

            mCurrentAnimator = ObjectAnimator.ofFloat(this, CREATE_TOUCH_RIPPLE, 0f, 1f);
            mCurrentAnimator.setDuration(DEFAULT_ANIM_DURATION);
        }

        if (!mCurrentAnimator.isRunning()) {
            mCurrentAnimator.start();
        }
    }

    void createTouchRipple(float value) {
        mAnimationValue = value;

        mTouchRipple.radius = 40f + (mAnimationValue * (END_RIPPLE_TOUCH_RADIUS - 40f));
        mBackgroundRipple.radius = mAnimationValue * (mViewSize * END_SCALE);

        int min = RIPPLE_TOUCH_MIN_ALPHA;
        int max = RIPPLE_TOUCH_MAX_ALPHA;
        int alpha = min + (int) (mAnimationValue * (max - min));
        mRipplePaint.setAlpha((max + min) - alpha);

        invalidateSelf();
    }

    void destroyTouchRipple(float value) {
        mAnimationValue = value;

        mTouchRipple.radius = END_RIPPLE_TOUCH_RADIUS + (mAnimationValue * (mViewSize * END_SCALE));

        mRipplePaint.setAlpha((int) (RIPPLE_TOUCH_MIN_ALPHA - (mAnimationValue * RIPPLE_TOUCH_MIN_ALPHA)));
        mRippleBackgroundPaint.setAlpha
                ((int) (RIPPLE_BACKGROUND_ALPHA - (mAnimationValue * RIPPLE_BACKGROUND_ALPHA)));

        invalidateSelf();
    }

    float getAnimationState() {
        return mAnimationValue;
    }

    void onFingerUp() {
        if (mCurrentAnimator != null) {
            mCurrentAnimator.end();
            mCurrentAnimator = null;
            createTouchRipple(END_RIPPLE_TOUCH_RADIUS);
        }

        mCurrentAnimator = ObjectAnimator.ofFloat(this, DESTROY_TOUCH_RIPPLE, 0f, 1f);
        mCurrentAnimator.setDuration(DEFAULT_ANIM_DURATION);
        mCurrentAnimator.addListener(new AnimatorListenerAdapter() {
            @Override
            public void onAnimationEnd(Animator animation) {
                super.onAnimationEnd(animation);
                mCurrentAnimator = null;
            }
        });
        mCurrentAnimator.start();
    }

    void onFingerMove(float x, float y) {
        mTouchRipple.cx = x;
        mTouchRipple.cy = y;

        invalidateSelf();
    }

    @Override
    public boolean setState(int[] stateSet) {
        if (mOriginalBackground != null) {
            return mOriginalBackground.setState(stateSet);
        }
        return super.setState(stateSet);
    }

    @Override
    public int[] getState() {
        if (mOriginalBackground != null) {
            return mOriginalBackground.getState();
        }
        return super.getState();
    }

    final static class Circle {
        float cx;
        float cy;
        float radius;

        public void draw(Canvas canvas, Paint paint) {
            canvas.drawCircle(cx, cy, radius, paint);
        }
    }

}
