package com.wusp.indicatorbox_library.Particle;

import android.animation.Animator;
import android.animation.ArgbEvaluator;
import android.animation.ValueAnimator;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Matrix;
import android.graphics.Paint;
import android.graphics.PorterDuff;
import android.graphics.PorterDuffXfermode;
import android.support.v4.view.MotionEventCompat;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import android.view.animation.DecelerateInterpolator;
import com.wusp.indicatorbox_library.R;

import java.util.ArrayList;
import java.util.Random;

/**
 * Created by wusp on 16/4/3.
 */
public class ParticleHeartView extends View implements View.OnTouchListener{
    /**These value is not open to be changed by user.*/
    //Used to control animation which is key of the whole view perform dynamic effects..
    private ValueAnimator mParticleAnimator;
    //Let the center bitmap perform a heart-beat movement.
    private ValueAnimator mHeartbeatAnimator;
    //Used to perform the particle motion.
    private ParticleField mParticleField;
    private ParticleField mDisappearField;
    //Used to controll how the particle will act.
    private ArrayList<ParticleMotionController> mParticleMotionControllers;
    private ArrayList<ParticleMotionController> mDisappearParticleMotionControllers;
    private ArrayList<ParticleInitializer> mParticleInitializers;
    //The center of this view.
    private int centerX;
    private int centerY;
    //Used to draw the center bitmap.
    private Matrix mMatrix;
    private Paint mPaint;
    private int centerBitmapHalfWidth;
    private int centerBitmapHalfHeight;
    private Bitmap bitmapDisappearDust;
    private int bitmapDisappearDustHalfWidth;
    private int bitmapDisappearDustHalfHeight;
    private int smallStarTranslateDistance = 40;
    private int bigStarTranslateDistance = 30;
    private int offsetForHeartCenter = 5;
    private boolean isQuiet = true;
    //Indicator whether user has praised this.
    private boolean isPraised = false;
    //Draw the follow circle
    private static final int END_COLOR = Color.parseColor("#11cd6e");
    private static final int START_COLOR = Color.parseColor("#a020ea");
    //Used to draw colorful particles.
    private int[] colorGroup = new int[]{
            Color.parseColor("#DAA520"),    //GoldEnrod
            Color.parseColor("#11cd6e")};    //Green
    /**
     * Follow Circle.
     */
    private ArgbEvaluator argbEvaluator;
    private Paint outerCirclePaint;
    private Paint innerCirclePaint;
    private float outerProgress;
    private float innerProgress;
    private int maxCircleRadius = 70;
    private Bitmap followCircleEmptyBitmap;
    private Canvas followCircleCanvas;
    private Bitmap mCenterBitmap;
    private long mDuration = 500;
    private Random random;
    private OnClickListener onClickListener;

    public ParticleHeartView(Context context) {
        super(context);
        init(context, null);
    }

    public ParticleHeartView(Context context, AttributeSet attrs) {
        super(context, attrs);
        init(context, attrs);
    }

    private void init(Context context, AttributeSet attrs) {
        mPaint = new Paint();
        //setFilterBitmap() and setAntiAlias used together to make drawing bitmap anti-aliased.
        mPaint.setFilterBitmap(true);
        mPaint.setAntiAlias(true);
        mMatrix = new Matrix();
        bitmapDisappearDust = BitmapFactory.decodeResource(getResources(), R.drawable.icon_red_dust);
        bitmapDisappearDustHalfWidth = bitmapDisappearDust.getWidth() / 2;
        bitmapDisappearDustHalfHeight = bitmapDisappearDust.getHeight() / 2;
        random = new Random(bitmapDisappearDust.getByteCount());
        mCenterBitmap = BitmapFactory.decodeResource(getResources(), R.drawable.icon_un_praised);
        centerBitmapHalfWidth = mCenterBitmap.getWidth() / 2;
        centerBitmapHalfHeight = mCenterBitmap.getHeight() / 2;
        this.setOnTouchListener(this);
        isPraised = false;
        //Follow circle
        outerCirclePaint = new Paint();
        innerCirclePaint = new Paint();
        outerCirclePaint.setStyle(Paint.Style.FILL);
        outerCirclePaint.setAntiAlias(true);
        innerCirclePaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.CLEAR));
        innerCirclePaint.setAntiAlias(true);
        argbEvaluator = new ArgbEvaluator();
    }

    /**
     * Convert animationFraction to some parameters used by Follow Circle.
     * @param animationFraction
     */
    private void outputFollowCircleParameter(float animationFraction){
        outerCirclePaint.setColor((Integer) argbEvaluator.evaluate(animationFraction, START_COLOR, END_COLOR));
        outerProgress = animationFraction * 0.3f + 0.7f;
        innerProgress = animationFraction;
    }

    /**
     * Init the particle field.
     */
    private void initParticleField(Context context, AttributeSet attributeSet) {
        BaseOneParticleInitializer initializerOne = new BaseOneParticleInitializer();
        initializerOne.setParticleBitmap(bitmapDisappearDust);
        BaseOneParticleInitializer initializerTwo = new BaseOneParticleInitializer();
        initializerTwo.setParticleBitmap(bitmapDisappearDust);
        BaseOneParticleInitializer initializerThree = new BaseOneParticleInitializer();
        initializerThree.setParticleBitmap(bitmapDisappearDust);
        BaseOneParticleInitializer initializerFour = new BaseOneParticleInitializer();
        initializerFour.setParticleBitmap(bitmapDisappearDust);
        BaseOneParticleInitializer initializerFive = new BaseOneParticleInitializer();
        initializerFive.setParticleBitmap(bitmapDisappearDust);
        BaseOneParticleInitializer initializerSix = new BaseOneParticleInitializer();
        initializerSix.setParticleBitmap(bitmapDisappearDust);
        BaseOneParticleInitializer initializerSeven = new BaseOneParticleInitializer();
        initializerSeven.setParticleBitmap(bitmapDisappearDust);
        BaseOneParticleInitializer initializerEight = new BaseOneParticleInitializer();
        initializerEight.setParticleBitmap(bitmapDisappearDust);
        mParticleInitializers = new ArrayList<>();
        mParticleInitializers.add(initializerOne);
        mParticleInitializers.add(initializerTwo);
        mParticleInitializers.add(initializerThree);
        mParticleInitializers.add(initializerFour);
        mParticleInitializers.add(initializerFive);
        mParticleInitializers.add(initializerSix);
        mParticleInitializers.add(initializerSeven);
        mParticleInitializers.add(initializerEight);
        //Default circle.
        mParticleField = new ParticleField.ViewGenerator()
                .setContext(context)
                .setAttributeSet(attributeSet)
                .setParticleNumbers(8)
                .generate();
        //Custom bitmap.
        mDisappearField = new ParticleField.ViewGenerator()
                .setContext(context)
                .setAttributeSet(attributeSet)
                .setParticleInitializers(mParticleInitializers)
                .generate();
    }

    /**
     * Init the how particles would act motion.
     */
    private void initParticleMotion(){
        mParticleMotionControllers = new ArrayList<>();
        mDisappearParticleMotionControllers = new ArrayList<>();
        int littleOffset = 10;
        int offsetOne = 0;
        int smallStarOffsetToCenter = offsetOne + 5;
        /**Small ones*/
        ParticleMotionController disappear1 = new ParticleMotionController();
        disappear1.setNumber(0);
        disappear1.addModifier(new TranslateModifier(centerX - smallStarOffsetToCenter - bitmapDisappearDustHalfWidth,
                centerY - bitmapDisappearDustHalfHeight,
                centerX - smallStarOffsetToCenter - smallStarTranslateDistance - bitmapDisappearDustHalfWidth,
                centerY - bitmapDisappearDustHalfHeight));
        mDisappearParticleMotionControllers.add(disappear1);
        ParticleMotionController disappear2 = new ParticleMotionController();
        disappear2.setNumber(1);
        disappear2.addModifier(new TranslateModifier(centerX - bitmapDisappearDustHalfWidth,
                centerY - smallStarOffsetToCenter - bitmapDisappearDustHalfHeight,
                centerX - bitmapDisappearDustHalfWidth,
                centerY - smallStarOffsetToCenter - smallStarTranslateDistance - bitmapDisappearDustHalfHeight));
        mDisappearParticleMotionControllers.add(disappear2);
        ParticleMotionController disappear3 = new ParticleMotionController();
        disappear3.setNumber(2);
        disappear3.addModifier(new TranslateModifier(centerX + smallStarOffsetToCenter - bitmapDisappearDustHalfWidth,
                centerY - bitmapDisappearDustHalfHeight,
                centerX + smallStarOffsetToCenter + smallStarTranslateDistance - bitmapDisappearDustHalfWidth,
                centerY - bitmapDisappearDustHalfHeight));
        mDisappearParticleMotionControllers.add(disappear3);
        ParticleMotionController disappear4 = new ParticleMotionController();
        disappear4.setNumber(3);
        disappear4.addModifier(new TranslateModifier(centerX - bitmapDisappearDustHalfWidth,
                centerY + smallStarOffsetToCenter - bitmapDisappearDustHalfHeight,
                centerX - bitmapDisappearDustHalfWidth,
                centerY + smallStarOffsetToCenter + smallStarTranslateDistance - bitmapDisappearDustHalfHeight));
        mDisappearParticleMotionControllers.add(disappear4);
        ParticleMotionController info1 = new ParticleMotionController();
        info1.setNumber(0);
        info1.addModifier(new TranslateModifier(centerX - littleOffset,
                centerY,
                centerX - smallStarTranslateDistance - littleOffset,
                centerY));
        mParticleMotionControllers.add(info1);
        ParticleMotionController info2 = new ParticleMotionController();
        info2.setNumber(1);
        info2.addModifier(new TranslateModifier(centerX,
                centerY  - littleOffset,
                centerX,
                centerY - smallStarTranslateDistance  - littleOffset));
        mParticleMotionControllers.add(info2);
        ParticleMotionController info3 = new ParticleMotionController();
        info3.setNumber(2);
        mParticleMotionControllers.add(info3);
        info3.addModifier(new TranslateModifier(centerX + littleOffset,
                centerY,
                centerX + smallStarTranslateDistance + littleOffset,
                centerY));
        ParticleMotionController info4 = new ParticleMotionController();
        info4.setNumber(3);
        info4.addModifier(new TranslateModifier(centerX,
                centerY + littleOffset,
                centerX,
                centerY + smallStarTranslateDistance + littleOffset));
        mParticleMotionControllers.add(info4);
        for (int i = 0; i < 4; i++){
            mParticleMotionControllers.get(i).addModifier(new ScaleModifier(0.8f, 0.8f + 0.2f * random.nextFloat()));
            mParticleMotionControllers.get(i).addModifier(new AlphaModifier(255, 100));
            random.setSeed(System.currentTimeMillis() + i * 100);
            mParticleMotionControllers.get(i).addModifier(getRandomArgbModifier());
        }
        for (int i = 0; i < 4; i++){
            mDisappearParticleMotionControllers.get(i).addModifier(new ScaleModifier(0.2f, 0.2f + 0.1f * random.nextFloat()));
            mDisappearParticleMotionControllers.get(i).addModifier(new AlphaModifier(255, 100));
        }
        /**Big ones*/
        ParticleMotionController disappear5 = new ParticleMotionController();
        disappear5.setNumber(4);
        disappear5.addModifier(new TranslateModifier(centerX - offsetOne - bitmapDisappearDustHalfWidth,
                centerY - offsetOne - bitmapDisappearDustHalfHeight - offsetForHeartCenter,
                centerX - offsetOne - bigStarTranslateDistance - bitmapDisappearDustHalfWidth,
                centerY - offsetOne - bigStarTranslateDistance - bitmapDisappearDustHalfHeight - offsetForHeartCenter));
        mDisappearParticleMotionControllers.add(disappear5);
        ParticleMotionController disappear6 = new ParticleMotionController();
        disappear6.setNumber(5);
        disappear6.addModifier(new TranslateModifier(centerX + offsetOne - bitmapDisappearDustHalfWidth,
                centerY - offsetOne - bitmapDisappearDustHalfHeight - offsetForHeartCenter,
                centerX + offsetOne + bigStarTranslateDistance - bitmapDisappearDustHalfWidth,
                centerY - offsetOne - bigStarTranslateDistance - bitmapDisappearDustHalfHeight - offsetForHeartCenter));
        mDisappearParticleMotionControllers.add(disappear6);
        ParticleMotionController disappear7 = new ParticleMotionController();
        disappear7.setNumber(6);
        disappear7.addModifier(new TranslateModifier(centerX + offsetOne - bitmapDisappearDustHalfWidth,
                centerY + offsetOne - bitmapDisappearDustHalfHeight - offsetForHeartCenter,
                centerX + offsetOne + bigStarTranslateDistance - bitmapDisappearDustHalfWidth,
                centerY + offsetOne + bigStarTranslateDistance - bitmapDisappearDustHalfHeight - offsetForHeartCenter));
        mDisappearParticleMotionControllers.add(disappear7);
        ParticleMotionController disappear8 = new ParticleMotionController();
        disappear8.setNumber(7);
        disappear8.addModifier(new TranslateModifier(centerX - offsetOne - bitmapDisappearDustHalfWidth,
                centerY + offsetOne - bitmapDisappearDustHalfHeight - offsetForHeartCenter,
                centerX - offsetOne - bigStarTranslateDistance - bitmapDisappearDustHalfWidth,
                centerY + offsetOne + bigStarTranslateDistance - bitmapDisappearDustHalfHeight - offsetForHeartCenter));
        mDisappearParticleMotionControllers.add(disappear8);
        ParticleMotionController info5 = new ParticleMotionController();
        info5.setNumber(4);
        info5.addModifier(new TranslateModifier(centerX - littleOffset,
                centerY - littleOffset,
                (float)(centerX - bigStarTranslateDistance * Math.sin(Math.PI / 4) - littleOffset ),
                (float)(centerY - bigStarTranslateDistance * Math.sin(Math.PI / 4)) - littleOffset));
        mParticleMotionControllers.add(info5);
        ParticleMotionController info6 = new ParticleMotionController();
        info6.setNumber(5);
        info6.addModifier(new TranslateModifier(centerX + littleOffset,
                centerY - littleOffset,
                (float)(centerX + bigStarTranslateDistance * Math.sin(Math.PI / 4) + littleOffset),
                (float)(centerY - bigStarTranslateDistance * Math.sin(Math.PI / 4)) - littleOffset));
        mParticleMotionControllers.add(info6);
        ParticleMotionController info7 = new ParticleMotionController();
        info7.setNumber(6);
        info7.addModifier(new TranslateModifier(centerX + littleOffset,
                centerY + littleOffset,
                (float)(centerX + bigStarTranslateDistance * Math.sin(Math.PI / 4) + littleOffset),
                (float)(centerY + bigStarTranslateDistance * Math.sin(Math.PI / 4)) + littleOffset));
        mParticleMotionControllers.add(info7);
        ParticleMotionController info8 = new ParticleMotionController();
        info8.setNumber(7);
        info8.addModifier(new TranslateModifier(centerX - littleOffset,
                centerY + littleOffset,
                (float)(centerX - bigStarTranslateDistance * Math.sin(Math.PI / 4) - littleOffset),
                (float)(centerY + bigStarTranslateDistance * Math.sin(Math.PI / 4)) + littleOffset));
        mParticleMotionControllers.add(info8);
        for (int i = 4; i < mParticleMotionControllers.size(); i++){
            if (i < 8) {
                mParticleMotionControllers.get(i).addModifier(new ScaleModifier(0.9f, 0.9f + 0.3f * random.nextFloat()));
                mParticleMotionControllers.get(i).addModifier(new AlphaModifier(240, 200));
                random.setSeed(System.currentTimeMillis() + i * 100);
                mParticleMotionControllers.get(i).addModifier(getRandomArgbModifier());
            }
        }
        for (int i = 4; i < mDisappearParticleMotionControllers.size(); i++){
            if (i < 8) {
                mDisappearParticleMotionControllers.get(i).addModifier(new ScaleModifier(0.3f, 0.3f + 0.1f * random.nextFloat()));
                mDisappearParticleMotionControllers.get(i).addModifier(new AlphaModifier(240, 200));
            }
        }
        /**Set to particle*/
        if (mParticleMotionControllers.size() != mParticleField.getmParticles().size()){
            return;
        }
        for (int i = 0; i < mParticleField.getmParticles().size(); i++){
            mParticleField.getmParticles().get(i).setModifiers(mParticleMotionControllers.get(i).getModifierList());
        }
        if (mDisappearParticleMotionControllers.size() != mDisappearField.getmParticles().size()){
            return;
        }
        for (int i = 0; i < mDisappearField.getmParticles().size(); i++){
            mDisappearField.getmParticles().get(i).setModifiers(mDisappearParticleMotionControllers.get(i).getModifierList());
        }
    }

    /**
     * To get random argb modifier which used to draw a color-gradient particle.
     * @return
     */
    private ArgbModifier getRandomArgbModifier(){
        int firstRandomInt = random.nextInt(2);
        int secondRandomInt = random.nextInt(2);
        return new ArgbModifier(colorGroup[firstRandomInt], colorGroup[secondRandomInt]);
    }

    /**
     * Init the valueAnimator.
     */
    private void initAnimation(long duration){
        //Define a value animator to controll the motion.
        mParticleAnimator = ValueAnimator.ofInt(0, (int)duration);
        mParticleAnimator.setDuration(duration);
        mParticleAnimator.setInterpolator(new DecelerateInterpolator());
        mParticleAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                if (isPraised) {
                    //Cause onAnimationStart change this.
                    outputFollowCircleParameter(animation.getAnimatedFraction());
                }
                postInvalidate();
            }
        });
        mParticleAnimator.addListener(new Animator.AnimatorListener() {
            @Override
            public void onAnimationStart(Animator animation) {
                //changed to praised.
                if (!isPraised) {
                    mCenterBitmap = BitmapFactory.decodeResource(getResources(), R.drawable.icon_praised);
                    centerBitmapHalfWidth = mCenterBitmap.getWidth() / 2;
                    centerBitmapHalfHeight = mCenterBitmap.getHeight() / 2;
                }else{
                    mCenterBitmap = BitmapFactory.decodeResource(getResources(), R.drawable.icon_un_praised);
                    centerBitmapHalfWidth = mCenterBitmap.getWidth() / 2;
                    centerBitmapHalfHeight = mCenterBitmap.getHeight() / 2;
                }
                isPraised = !isPraised;
                isQuiet = !isQuiet;
            }

            @Override
            public void onAnimationEnd(Animator animation) {
                isQuiet = !isQuiet;
            }

            @Override
            public void onAnimationCancel(Animator animation) {
                isQuiet = !isQuiet;
            }

            @Override
            public void onAnimationRepeat(Animator animation) {

            }
        });
        mHeartbeatAnimator = GeneralAnimatorGenerator.dampingValueAnimator(duration);
        mHeartbeatAnimator.addListener(new Animator.AnimatorListener() {
            @Override
            public void onAnimationStart(Animator animation) {
            }

            @Override
            public void onAnimationEnd(Animator animation) {

            }

            @Override
            public void onAnimationCancel(Animator animation) {

            }

            @Override
            public void onAnimationRepeat(Animator animation) {

            }
        });
    }

    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        followCircleEmptyBitmap = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888);
        followCircleCanvas = new Canvas(followCircleEmptyBitmap);
    }

    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        super.onLayout(changed, l, t, r, b);
        centerX = (r - l) / 2;
        centerY = (b - t) / 2;
        initParticleField(getContext(), null);
        initParticleMotion();
        initAnimation(mDuration);
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        /** CenterBitmap scale motion */
        if (mCenterBitmap != null) {
            mMatrix.reset();
            mMatrix.postScale(mHeartbeatAnimator.getAnimatedFraction() * 0.1f + 0.5f,
                    mHeartbeatAnimator.getAnimatedFraction() * 0.1f + 0.5f,
                    centerBitmapHalfWidth, centerBitmapHalfHeight);
            mMatrix.postTranslate(centerX - centerBitmapHalfWidth, centerY - centerBitmapHalfHeight);
            canvas.drawBitmap(mCenterBitmap, mMatrix, mPaint);
        }
        /**To draw the follow circle*/
        if (outerProgress < 1 && isPraised) {
            followCircleCanvas.drawCircle(getWidth() / 2, getHeight() / 2, outerProgress * maxCircleRadius / 2, outerCirclePaint);
            followCircleCanvas.drawCircle(getWidth() / 2, getHeight() / 2, innerProgress * maxCircleRadius / 2, innerCirclePaint);
            canvas.drawBitmap(followCircleEmptyBitmap, 0, 0, null);
        }
        /** Draw the particle party */
        if (!isQuiet) {
            if (isPraised) {
                //This control makes the particle do not show during the animation first phase.
                for (int i = 0; i < mParticleField.getmParticles().size(); i++) {
                    if ((mParticleAnimator.getAnimatedFraction() < 0.3f && i >= 4 && i < 8)
                            || (mParticleAnimator.getAnimatedFraction() < 0.5f && i < 4))
                        continue;
                    mParticleField.getmParticles().get(i).update(mParticleAnimator.getAnimatedFraction());
                    mParticleField.getmParticles().get(i).draw(canvas);
                }
            }else{
                for (int i = 0; i < mDisappearField.getmParticles().size(); i++) {
                    if ((mParticleAnimator.getAnimatedFraction() < 0.3f && i >= 4 && i < 8)
                            || (mParticleAnimator.getAnimatedFraction() < 0.5f && i < 4))
                        continue;
                    mDisappearField.getmParticles().get(i).update(mParticleAnimator.getAnimatedFraction());
                    mDisappearField.getmParticles().get(i).draw(canvas);
                }
            }
        }
    }

    public void startAnimation(){
        if (mParticleAnimator != null){
            if (mParticleAnimator.isRunning()){
                mParticleAnimator.end();
                mParticleAnimator.cancel();
            }
            mParticleAnimator.start();
        }
        if (mHeartbeatAnimator != null){
            if (mHeartbeatAnimator.isRunning()){
                mHeartbeatAnimator.end();
                mHeartbeatAnimator.cancel();
            }
            mHeartbeatAnimator.start();
        }
    }

    public long getmDuration() {
        return mDuration;
    }

    public void setmDuration(long mDuration) {
        this.mDuration = mDuration;
    }

    public Bitmap getmCenterBitmap() {
        return mCenterBitmap;
    }

    public void setmCenterBitmap(Bitmap mCenterBitmap) {
        this.mCenterBitmap = mCenterBitmap;
    }

    public OnClickListener getOnClickListener() {
        return onClickListener;
    }

    @Override
    public void setOnClickListener(OnClickListener onClickListener) {
        this.onClickListener = onClickListener;
    }

    @Override
    public boolean onTouch(View v, MotionEvent event) {
        switch(MotionEventCompat.getActionMasked(event)){
            case MotionEvent.ACTION_DOWN:
                if (onClickListener != null) {
                    onClickListener.onClick(v);
                }
                startAnimation();
                callOnClick();
                return true;
            case MotionEvent.ACTION_MOVE:
                return true;
            case MotionEvent.ACTION_UP:

                return false;
        }
        return false;
    }
}