package com.marshalchen.common.uimodule.androidviewhover;

import android.content.Context;
import android.graphics.Bitmap;
import android.os.Build;
import android.util.AttributeSet;
import android.view.GestureDetector;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewTreeObserver;
import android.view.animation.Interpolator;
import android.widget.ImageView;
import android.widget.RelativeLayout;
import com.marshalchen.common.uimodule.androidanimations.Techniques;
import com.marshalchen.common.uimodule.androidanimations.YoYo;
import com.marshalchen.common.uimodule.androidviewhover.proxy.AnimationProxy;
import com.marshalchen.common.uimodule.androidviewhover.tools.Blur;
import com.marshalchen.common.uimodule.androidviewhover.tools.Util;
import com.nineoldandroids.animation.Animator;
import com.nineoldandroids.animation.AnimatorSet;
import com.nineoldandroids.animation.ObjectAnimator;


import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;

public class BlurLayout extends RelativeLayout {


    private View mHoverView;

    private boolean enableBlurBackground = true;
    private int mBlurRadius = 10;
    private ImageView mBlurImage;

    private static long DURATION = 500;

    private ArrayList<Animator> mPlayingAnimators = new ArrayList<Animator>();

    private ArrayList<Animator> mAppearingAnimators = new ArrayList<Animator>();
    private ArrayList<Animator> mDisappearingAnimators = new ArrayList<Animator>();

    private ArrayList<AppearListener> mAppearListeners = new ArrayList<AppearListener>();
    private ArrayList<DisappearListener> mDisappearListeners = new ArrayList<DisappearListener>();

    private boolean enableBackgroundZoom = false;
    private float mZoomRatio = 1.14f;

    private boolean enableTouchEvent = true;

    private Animator mHoverAppearAnimator;
    private YoYo.AnimationComposer mHoverAppearAnimationComposer;

    private Animator mHoverDisappearAnimator;
    private YoYo.AnimationComposer mHoverDisappearAnimationComposer;

    private HashMap<View, ArrayList<AnimationProxy>> mChildAppearAnimators = new HashMap<View, ArrayList<AnimationProxy>>();
    private HashMap<View, ArrayList<AnimationProxy>> mChildDisappearAnimators = new HashMap<View, ArrayList<AnimationProxy>>();

    private long mBlurDuration = DURATION;

    public enum HOVER_STATUS {
        APPEARING, APPEARED, DISAPPEARING, DISAPPEARED
    };

    private HOVER_STATUS mHoverStatus = HOVER_STATUS.DISAPPEARED;

    public BlurLayout(Context context) {
        super(context);
    }

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

    public BlurLayout(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        return enableTouchEvent && gestureDetector.onTouchEvent(event);
    }

    private GestureDetector gestureDetector = new GestureDetector(getContext(), new BlurLayoutDetector());

    class BlurLayoutDetector extends GestureDetector.SimpleOnGestureListener {

        @Override
        public boolean onDown(MotionEvent e) {
            return true;
        }

        @Override
        public boolean onSingleTapUp(MotionEvent e) {
            if(hover())
                return true;
            else
                return super.onSingleTapConfirmed(e);
        }
    };

    public void showHover(){
        hover();
    }

    /**
     * Let hover show.
     * @return
     */
    private boolean hover(){
        if(mHoverView == null)  return false;

        if(getHoverStatus() != HOVER_STATUS.DISAPPEARED || !mPlayingAnimators.isEmpty())    return true;

        removeView(mBlurImage);
        if(enableBlurBackground)
            addBlurImage();

        if(mHoverView.getParent() != null){
            ((ViewGroup)(mHoverView.getParent())).removeView(mHoverView);
        }

        addView(mHoverView, getFullParentSizeLayoutParams());

        mHoverView.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
            @Override
            public void onGlobalLayout() {

                startChildrenAppearAnimations();

                startBlurImageAppearAnimator();

                startHoverAppearAnimator();

                if(Build.VERSION.SDK_INT >= 16)
                    mHoverView.getViewTreeObserver().removeOnGlobalLayoutListener(this);
                else
                    mHoverView.getViewTreeObserver().removeGlobalOnLayoutListener(this);
            }
        });
        return true;

    }

    /**
     * Let hover view dismiss.
     * Notice: only when hover view status is appeared, then, this may work.
     */
    public void dismissHover(){
        if(getHoverStatus() != HOVER_STATUS.APPEARED || !mPlayingAnimators.isEmpty())
            return;

        startBlurImageDisappearAnimator();

        startHoverDisappearAnimator();

        startChildrenDisappearAnimations();
    }

    public void toggleHover(){
        if(getHoverStatus() == HOVER_STATUS.DISAPPEARED)
            showHover();
        else if(getHoverStatus() == HOVER_STATUS.APPEARED)
            dismissHover();
    }

    /**
     * get currently hover status.
     * @return
     */
    public HOVER_STATUS getHoverStatus(){
        return mHoverStatus;
    }

    private void addBlurImage(){
        Bitmap b = Util.getViewBitmap(this);
        if(b == null)
            return;
        Bitmap bm = Blur.apply(getContext(), b, mBlurRadius);
        ImageView im = new ImageView(getContext());
        im.setImageBitmap(bm);
        mBlurImage = im;
        this.addView(im);
    }

    /**
     * set background blur duration.
     * @param duration
     */
    public void setBlurDuration(long duration){
        if(duration > 100)
            mBlurDuration = duration;
    }


    /**
     * set background blur radius.
     * @param radius radius to be used for the gaussian blur operation, integer between 0 and 25 (inclusive)
     */
    public void setBlurRadius(int radius) {
        if(radius < 0 || radius > 25){
            throw new IllegalArgumentException("Radius must be between 0 and 25 (inclusive)");
        }
        this.mBlurRadius = radius;
    }

    /**
     * bind a hover view with BlurLayout.
     * @param hover
     */
    public void setHoverView(final View hover){
        mHoverView = hover;

        if(mHoverView == null)  return;

        if(mHoverAppearAnimator != null)
            mHoverAppearAnimator.setTarget(mHoverView);

        mHoverView.setOnClickListener(new OnClickListener() {
            @Override
            public void onClick(View v) {

                dismissHover();

            }
        });
    }

    /**
     * Sets whether or not touching the BlurLayout will trigger the Hover View and blur effect
     * @param enableTouchEvent
     */
    public void enableTouchEvent(boolean enableTouchEvent) {
        this.enableTouchEvent = enableTouchEvent;
    }

    public void enableBlurBackground(boolean enable){
        enableBlurBackground = enable;
    }

    public void enableZoomBackground(boolean enable) {
        enableBackgroundZoom = enable;
    }

    public void setBlurZoomRatio(float ratio){
        if(ratio < 0)
            throw new IllegalArgumentException("Can not set ratio less than 0");
        mZoomRatio = ratio;
    }

    private void startBlurImageAppearAnimator(){
        if(!enableBlurBackground || mBlurImage == null)    return;

        AnimatorSet set = new AnimatorSet();
         if(enableBackgroundZoom){
            set.playTogether(
                    ObjectAnimator.ofFloat(mBlurImage, "alpha", 0.8f, 1f),
                    ObjectAnimator.ofFloat(mBlurImage, "scaleX", 1f, mZoomRatio),
                    ObjectAnimator.ofFloat(mBlurImage, "scaleY", 1f, mZoomRatio)
            );
        }
        else{
            set.playTogether(
                    ObjectAnimator.ofFloat(mBlurImage, "alpha", 0f, 1f)
            );
        }
        set.addListener(mGlobalListener);
        set.addListener(mGlobalAppearingAnimators);
        set.setDuration(mBlurDuration);
        set.start();
    }

    private void startBlurImageDisappearAnimator(){
        if(!enableBlurBackground || mBlurImage == null)    return;

        AnimatorSet set = new AnimatorSet();
        if(enableBackgroundZoom)
            set.playTogether(
                    ObjectAnimator.ofFloat(mBlurImage, "alpha", 1f, 0.8f),
                    ObjectAnimator.ofFloat(mBlurImage, "scaleX", mZoomRatio, 1f),
                    ObjectAnimator.ofFloat(mBlurImage, "scaleY", mZoomRatio, 1f)
            );
        else
            set.playTogether(
                    ObjectAnimator.ofFloat(mBlurImage, "alpha", 1f, 0f)
            );

        set.addListener(mGlobalListener);
        set.addListener(mGlobalDisappearAnimators);
        set.setDuration(mBlurDuration);
        set.start();
    }

    private void startHoverAppearAnimator(){
        if(mHoverAppearAnimator != null)
            mHoverAppearAnimator.start();

        if(mHoverAppearAnimationComposer != null)
            mHoverAppearAnimationComposer.playOn(mHoverView);
    }

    private void startHoverDisappearAnimator(){
        if(mHoverDisappearAnimator != null)
            mHoverDisappearAnimator.start();
        if(mHoverDisappearAnimationComposer != null)
            mHoverDisappearAnimationComposer.playOn(mHoverView);
    }

    private void startChildrenAppearAnimations(){
        for(Map.Entry<View, ArrayList<AnimationProxy>> entry : mChildAppearAnimators.entrySet()){
            for(AnimationProxy animator : entry.getValue()){
                animator.start();
            }
        }
    }

    private void startChildrenDisappearAnimations(){
        for(View view : mChildDisappearAnimators.keySet()){
            for(AnimationProxy animator : mChildDisappearAnimators.get(view)){
                animator.start();
            }
        }
    }

    public interface AppearListener {
        public void onStart();
        public void onEnd();
    }

    public interface DisappearListener {
        public void onStart();
        public void onEnd();
    }

    public void addAppearListener(AppearListener l){
        mAppearListeners.add(l);
    }

    public void removeAppearListener(AppearListener l){
        mAppearListeners.remove(l);
    }

    public void addDisappearListener(DisappearListener l){
        mDisappearListeners.add(l);
    }

    public void removeDisappearListener(DisappearListener l){
        mDisappearingAnimators.remove(l);
    }

    public Animator.AnimatorListener mGlobalAppearingAnimators = new Animator.AnimatorListener() {
        @Override
        public void onAnimationStart(Animator animation) {
            mAppearingAnimators.add(animation);
            if(mAppearingAnimators.size() == 1){
                mHoverStatus = HOVER_STATUS.APPEARING;
                for(AppearListener l : mAppearListeners){
                    l.onStart();
                }
            }
        }

        @Override
        public void onAnimationEnd(Animator animation) {
            mAppearingAnimators.remove(animation);
            if(mAppearListeners.isEmpty()){
                mHoverStatus = HOVER_STATUS.APPEARED;
                for(AppearListener l : mAppearListeners){
                    l.onEnd();
                }
            }
        }

        @Override
        public void onAnimationCancel(Animator animation) {

        }

        @Override
        public void onAnimationRepeat(Animator animation) {

        }
    };

    public Animator.AnimatorListener mGlobalDisappearAnimators = new Animator.AnimatorListener() {
        @Override
        public void onAnimationStart(Animator animation) {
            mDisappearingAnimators.add(animation);
            if(mDisappearListeners.size() == 1){
                for(DisappearListener l : mDisappearListeners){
                    mHoverStatus = HOVER_STATUS.DISAPPEARING;
                    l.onStart();
                }
            }
        }

        @Override
        public void onAnimationEnd(Animator animation) {
            mDisappearingAnimators.remove(animation);
            if(mPlayingAnimators.isEmpty()){
                mHoverStatus = HOVER_STATUS.DISAPPEARED;
                removeView(mBlurImage);
                removeView(mHoverView);
                for(DisappearListener l : mDisappearListeners){
                    l.onEnd();
                }
            }
        }

        @Override
        public void onAnimationCancel(Animator animation) {

        }

        @Override
        public void onAnimationRepeat(Animator animation) {

        }
    };

    public Animator.AnimatorListener mGlobalListener = new Animator.AnimatorListener() {
        @Override
        public void onAnimationStart(Animator animation) {
            mPlayingAnimators.add(animation);
        }

        @Override
        public void onAnimationEnd(Animator animation) {
            mPlayingAnimators.remove(animation);
        }

        @Override
        public void onAnimationCancel(Animator animation) {

        }

        @Override
        public void onAnimationRepeat(Animator animation) {

        }
    };

    public void setHoverAppearAnimator(Techniques technique){
        setHoverAppearAnimator(technique, DURATION);
    }

    public void setHoverAppearAnimator(Techniques technique, long duration){
        setHoverAppearAnimator(technique, duration, 0, null);
    }

    public void setHoverAppearAnimator(Techniques technique, long duration, long delay, Interpolator interpolator){
        setHoverAppearAnimator(technique, duration, delay, interpolator, new Animator.AnimatorListener[]{});
    }

    public void setHoverAppearAnimator(Techniques technique, long duration, long delay, Interpolator interpolator, Animator.AnimatorListener... listeners){
        mHoverAppearAnimator = null;
        mHoverAppearAnimationComposer = YoYo.with(technique).delay(delay).duration(duration).interpolate(interpolator);

        for(Animator.AnimatorListener l : listeners)
            mHoverAppearAnimationComposer.withListener(l);
        mHoverAppearAnimationComposer.withListener(mGlobalListener);
        mHoverAppearAnimationComposer.withListener(mGlobalAppearingAnimators);
    }

    public void setHoverAppearAnimator(Animator animator){
        mHoverAppearAnimationComposer = null;
        mHoverAppearAnimator = animator;

        animator.addListener(mGlobalListener);
        animator.addListener(mGlobalAppearingAnimators);
    }



    public void setHoverDisappearAnimator(Techniques technique){
        setHoverDisappearAnimator(technique, DURATION);
    }

    public void setHoverDisappearAnimator(Techniques technique, long duration){
        setHoverDisappearAnimator(technique, duration, 0, null);
    }

    public void setHoverDisappearAnimator(Techniques technique, long duration, long delay, Interpolator interpolator){
        setHoverDisappearAnimator(technique, duration, delay, interpolator, new Animator.AnimatorListener[]{});
    }

    public void setHoverDisappearAnimator(Techniques technique, long duration, long delay, Interpolator interpolator, Animator.AnimatorListener... listeners){
        mHoverDisappearAnimator = null;
        mHoverDisappearAnimationComposer = YoYo.with(technique).delay(delay).duration(duration).interpolate(interpolator);

        for(Animator.AnimatorListener l : listeners)
            mHoverDisappearAnimationComposer.withListener(l);

        mHoverDisappearAnimationComposer.withListener(mGlobalListener);
        mHoverDisappearAnimationComposer.withListener(mGlobalDisappearAnimators);
    }

    public void setHoverDisappearAnimator(Animator animator){
        mHoverDisappearAnimationComposer = null;
        mHoverDisappearAnimator = animator;
        mHoverDisappearAnimator.addListener(mGlobalListener);
        mHoverDisappearAnimator.addListener(mGlobalDisappearAnimators);
    }



    public void addChildAppearAnimator(View hoverView, int resId, Techniques technique){
        addChildAppearAnimator(hoverView, resId, technique, DURATION);
    }

    public void addChildAppearAnimator(View hoverView, int resId, Techniques technique, long duration){
        addChildAppearAnimator(hoverView, resId, technique, duration, 0);
    }

    public void addChildAppearAnimator(View hoverView, int resId, Techniques technique, long duration, long delay){
        addChildAppearAnimator(hoverView, resId, technique, duration, delay, true, null);
    }

    public void addChildAppearAnimator(View hoverView, int resId, Techniques technique, long duration, long delay, boolean hiddenWhenDelaying){
        addChildAppearAnimator(hoverView, resId, technique, duration, delay, hiddenWhenDelaying, null);
    }

    public void addChildAppearAnimator(View hoverView, int resId, Techniques technique, long duration, long delay, boolean hiddenWhenDelaying, Interpolator interpolator){
        addChildAppearAnimator(hoverView, resId, technique, duration, delay, hiddenWhenDelaying, interpolator, new Animator.AnimatorListener[]{});
    }

    public void addChildAppearAnimator(View hoverView, int resId, Techniques technique, long duration, long delay, boolean hiddenWhenDelaying, Interpolator interpolator, Animator.AnimatorListener... listeners){
        AnimationProxy executor = AnimationProxy.build(hoverView, resId, technique, duration, delay, hiddenWhenDelaying, interpolator, listeners);

        View child = executor.getTarget();

        if(mChildAppearAnimators.get(child) == null)
            mChildAppearAnimators.put(child, new ArrayList<AnimationProxy>());

        executor.withListener(mGlobalListener);
        executor.withListener(mGlobalAppearingAnimators);
        child.setVisibility(INVISIBLE);

        mChildAppearAnimators.get(child).add(executor);
    }

    public void addChildAppearAnimator(View hoverView, int childId, Animator animator){
        AnimationProxy executor = AnimationProxy.build(hoverView, childId, animator);

        View child = executor.getTarget();

        if(mChildAppearAnimators.get(child) == null)
            mChildAppearAnimators.put(child, new ArrayList<AnimationProxy>());

        executor.withListener(mGlobalListener);
        executor.withListener(mGlobalAppearingAnimators);
        mChildAppearAnimators.get(child).add(executor);
    }

    public void addChildDisappearAnimator(View hoverView, int resId, Techniques technique){
        addChildDisappearAnimator(hoverView, resId, technique, DURATION);
    }

    public void addChildDisappearAnimator(View hoverView, int resId, Techniques technique, long duration){
        addChildDisappearAnimator(hoverView, resId, technique, duration, 0, false);
    }

    public void addChildDisappearAnimator(View hoverView, int resId, Techniques technique, long duration, long delay){
        addChildDisappearAnimator(hoverView, resId, technique, duration, delay, false, null);
    }

    public void addChildDisappearAnimator(View hoverView, int resId, Techniques technique, long duration, long delay, boolean invisibleWhenDelaying){
        addChildDisappearAnimator(hoverView, resId, technique, duration, delay,invisibleWhenDelaying, null);
    }

    public void addChildDisappearAnimator(View hoverView, int resId, Techniques technique, long duration, long delay, boolean invisibleWhenDelaying, Interpolator interpolator){
        addChildDisappearAnimator(hoverView, resId, technique, duration, delay, invisibleWhenDelaying, interpolator, new Animator.AnimatorListener[]{});
    }

    public void addChildDisappearAnimator(View hoverView, int resId, Techniques technique, long duration, long delay, boolean invisibleWhenDelaying, Interpolator interpolator, Animator.AnimatorListener... listeners){

        AnimationProxy executor = AnimationProxy.build(hoverView, resId, technique, duration, delay, invisibleWhenDelaying, interpolator, listeners);

        View child = executor.getTarget();
        if(mChildDisappearAnimators.containsKey(child) == false)
            mChildDisappearAnimators.put(child, new ArrayList<AnimationProxy>());

        executor.withListener(mGlobalListener);
        executor.withListener(mGlobalDisappearAnimators);
        mChildDisappearAnimators.get(child).add(executor);
    }

    public void addChildDisappearAnimator(View hoverView, int childId, Animator animator){
        AnimationProxy executor = AnimationProxy.build(hoverView, childId, animator);

        View child = executor.getTarget();

        if(mChildDisappearAnimators.get(child) == null)
            mChildDisappearAnimators.put(child, new ArrayList<AnimationProxy>());

        executor.withListener(mGlobalListener);
        executor.withListener(mGlobalDisappearAnimators);
        mChildDisappearAnimators.get(child).add(executor);
    }

    public LayoutParams getFullParentSizeLayoutParams(){
        LayoutParams pm = (LayoutParams)this.generateDefaultLayoutParams();
        pm.width = this.getWidth();
        pm.height = this.getHeight();
        return pm;
    }

    public static void setGlobalDefaultDuration(long duration){
        if(duration < 100)
            throw new IllegalArgumentException("Duration can not be set to less than 100");
        DURATION = duration;
    }


}