package com.kyleduo.blurpopupwindow.library;

import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.ObjectAnimator;
import android.app.Activity;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.PixelFormat;
import android.graphics.drawable.Drawable;
import android.os.AsyncTask;
import android.os.Build;
import android.support.annotation.AnyThread;
import android.support.annotation.CallSuper;
import android.support.annotation.NonNull;
import android.util.DisplayMetrics;
import android.util.Log;
import android.view.Display;
import android.view.Gravity;
import android.view.KeyEvent;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.view.WindowManager;
import android.widget.FrameLayout;
import android.widget.ImageView;

import java.lang.ref.WeakReference;
import java.lang.reflect.Method;

/**
 * PopupWindow with blurred below view.
 * Created by kyle on 2017/3/14.
 */

@SuppressWarnings("ALL")
public class BlurPopupWindow extends FrameLayout {
    private static final String TAG = "BlurPopupWindow";

    private static final float DEFAULT_BLUR_RADIUS = 10;
    private static final float DEFAULT_SCALE_RATIO = 0.4f;
    private static final long DEFAULT_ANIMATION_DURATION = 300;

    public interface OnDismissListener {
        void onDismiss(BlurPopupWindow popupWindow);
    }

    private Activity mActivity;
    protected ImageView mBlurView;
    protected FrameLayout mContentLayout;
    private boolean mAnimating;

    private WindowManager mWindowManager;

    private View mContentView;
    private int mTintColor;
    private View mAnchorView;
    private float mBlurRadius;
    private float mScaleRatio;
    private long mAnimationDuration;
    private boolean mDismissOnTouchBackground;
    private boolean mDismissOnClickBack;
    private OnDismissListener mOnDismissListener;

    public BlurPopupWindow(@NonNull Context context) {
        super(context);
        init();
    }

    private void init() {
        if (!(getContext() instanceof Activity)) {
            throw new IllegalArgumentException("Context must be Activity");
        }
        mActivity = (Activity) getContext();
        mWindowManager = mActivity.getWindowManager();

        mBlurRadius = DEFAULT_BLUR_RADIUS;
        mScaleRatio = DEFAULT_SCALE_RATIO;
        mAnimationDuration = DEFAULT_ANIMATION_DURATION;

        setFocusable(true);
        setFocusableInTouchMode(true);

        mContentLayout = new FrameLayout(getContext());
        LayoutParams lp = new LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
        addView(mContentLayout, lp);

        mBlurView = new ImageView(mActivity);
        mBlurView.setScaleType(ImageView.ScaleType.FIT_XY);
        lp = new LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
        lp.gravity = Gravity.BOTTOM;
        mBlurView.setLayoutParams(lp);
        mContentLayout.addView(mBlurView);

        mContentView = createContentView(mContentLayout);
        if (mContentView != null) {
            mContentLayout.addView(mContentView);
        }
    }

    /**
     * Override this to create custom content.
     *
     * @param parent the parent where content view would be.
     * @return
     */
    protected View createContentView(ViewGroup parent) {
        return null;
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        if (mAnimating || !mDismissOnTouchBackground) {
            return super.onTouchEvent(event);
        }
        if (event.getAction() == MotionEvent.ACTION_UP) {
            dismiss();
        }
        return true;
    }

    @Override
    public boolean onKeyUp(int keyCode, KeyEvent event) {
        if (mAnimating || !mDismissOnClickBack) {
            return super.onKeyUp(keyCode, event);
        }
        if (keyCode == KeyEvent.KEYCODE_BACK) {
            dismiss();
            return true;
        }
        return super.onKeyUp(keyCode, event);
    }

    public void setContentView(View contentView) {
        if (contentView == null) {
            throw new IllegalArgumentException("contentView can not be null");
        }
        if (mContentView != null) {
            if (mContentView.getParent() != null) {
                ((ViewGroup) mContentView.getParent()).removeView(mContentView);
            }
            mContentView = null;
        }
        mContentView = contentView;
        mContentLayout.addView(mContentView);
    }

    public View getContentView() {
        return mContentView;
    }

    public void show() {
        if (mAnimating) {
            return;
        }

        WindowManager.LayoutParams params = new WindowManager.LayoutParams();
        params.width = WindowManager.LayoutParams.MATCH_PARENT;
        params.height = WindowManager.LayoutParams.MATCH_PARENT;
        params.format = PixelFormat.RGBA_8888;

        int statusBarHeight = 0;
        int navigationBarHeight = BlurPopupWindow.getNaviHeight(mActivity);
        int resourceId = getResources().getIdentifier("status_bar_height", "dimen", "android");
        if (resourceId > 0) {
            statusBarHeight = getResources().getDimensionPixelSize(resourceId);
        }

        int trimTopHeight = statusBarHeight;
        int trimBottomHeight = 0;
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {

            // No need to trim status bar height in SDK > 21.
            trimTopHeight = 0;

            WindowManager.LayoutParams lp = mActivity.getWindow().getAttributes();
            if ((lp.flags & WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION) == 0 && Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
                trimBottomHeight = navigationBarHeight;
            }

            // This line will cause decor view fill all the screen, even if FLAG_TRANSLUCENT_NAVIGATION
            // was not set.
            params.flags = lp.flags;

            if (trimBottomHeight > 0) {

                // If trimBottomHeight > 0, it means that we cut navigation bar off and we need shrink
                // popup windows' content height by increase bottom padding.
                setPadding(getPaddingLeft(), getPaddingTop(), getPaddingRight(), getPaddingBottom() + navigationBarHeight);
            } else {

                // If navigation is showing on the screen, whether translucent or not, we should move contentView
                // on top of it.
                boolean moveContent = false;
                if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
                    moveContent = true;
                } else if (navigationBarHeight > 0 && (lp.flags & WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION) != 0) {
                    // Navigation feature diffs from v19 to v21.
                    moveContent = true;
                }
                if (navigationBarHeight > 0 && moveContent) {
                    if (mContentView != null) {
                        MarginLayoutParams layoutParams = (MarginLayoutParams) mContentView.getLayoutParams();
                        layoutParams.bottomMargin += navigationBarHeight;
                    }
                }
            }
        }

        new BlurTask(mActivity.getWindow().getDecorView(), trimTopHeight, trimBottomHeight, this, new BlurTask.BlurTaskCallback() {
            @Override
            public void onBlurFinish(Bitmap bitmap) {
                onBlurredImageGot(bitmap);
            }
        }).execute();

        mWindowManager.addView(this, params);

        ObjectAnimator showAnimator = createShowAnimator();
        if (showAnimator != null) {
            mAnimating = true;
            showAnimator.addListener(new AnimatorListenerAdapter() {
                @Override
                public void onAnimationCancel(Animator animation) {
                    mAnimating = false;
                    requestFocus();
                }

                @Override
                public void onAnimationEnd(Animator animation) {
                    mAnimating = false;
                    requestFocus();
                }
            });
            showAnimator.start();
        }
        onShow();
    }

    public void dismiss() {
        if (mAnimating) {
            return;
        }
        onDismiss();
        ObjectAnimator animator = createDismissAnimator();
        if (animator == null) {
            mWindowManager.removeView(this);
        } else {
            mAnimating = true;
            ObjectAnimator.ofFloat(mBlurView, "alpha", mBlurView.getAlpha(), 0).setDuration(getAnimationDuration()).start();
            animator.addListener(new AnimatorListenerAdapter() {
                @Override
                public void onAnimationEnd(Animator animation) {
                    removeSelf();
                }

                @Override
                public void onAnimationCancel(Animator animation) {
                    removeSelf();
                }

                private void removeSelf() {
                    try {
                        mWindowManager.removeView(BlurPopupWindow.this);
                    } catch (Exception e) {
                        e.printStackTrace();
                    } finally {
                        mAnimating = false;
                    }
                }
            });
            animator.start();
        }
    }

    protected void onBlurredImageGot(Bitmap bitmap) {
        mBlurView.setImageBitmap(bitmap);
        if (!mAnimating) {
            ObjectAnimator.ofFloat(mBlurView, "alpha", 0, 1f).setDuration(getAnimationDuration()).start();
        }
    }

    /**
     * When executing show method in this method, should override {@link BlurPopupWindow#createShowAnimator()}
     * and return null as well.
     */
    protected void onShow() {
    }

    /**
     * Do not start any animation in this method. use {@link BlurPopupWindow#createDismissAnimator()} instead.
     */
    @CallSuper
    protected void onDismiss() {
        if (mOnDismissListener != null) {
            mOnDismissListener.onDismiss(this);
        }
    }

    protected ObjectAnimator createShowAnimator() {
        return ObjectAnimator.ofFloat(mContentLayout, "alpha", 0, 1.f).setDuration(getAnimationDuration());
    }

    protected ObjectAnimator createDismissAnimator() {
        return ObjectAnimator.ofFloat(mContentLayout, "alpha", mContentLayout.getAlpha(), 0).setDuration(getAnimationDuration());
    }

    public int getTintColor() {
        return mTintColor;
    }

    public void setTintColor(int tintColor) {
        mTintColor = tintColor;
    }

    public View getAnchorView() {
        return mAnchorView;
    }

    public void setAnchorView(View anchorView) {
        mAnchorView = anchorView;
    }

    @AnyThread
    public float getBlurRadius() {
        return mBlurRadius;
    }

    public void setBlurRadius(float blurRadius) {
        mBlurRadius = blurRadius;
    }

    @AnyThread
    public float getScaleRatio() {
        return mScaleRatio;
    }

    public void setScaleRatio(float scaleRatio) {
        mScaleRatio = scaleRatio;
    }

    public long getAnimationDuration() {
        return mAnimationDuration;
    }

    public void setAnimationDuration(long animationDuration) {
        mAnimationDuration = animationDuration;
    }

    public boolean isDismissOnTouchBackground() {
        return mDismissOnTouchBackground;
    }

    public void setDismissOnTouchBackground(boolean dismissOnTouchBackground) {
        mDismissOnTouchBackground = dismissOnTouchBackground;
    }

    public boolean isDismissOnClickBack() {
        return mDismissOnClickBack;
    }

    public void setDismissOnClickBack(boolean dismissOnClickBack) {
        mDismissOnClickBack = dismissOnClickBack;
    }

    public OnDismissListener getOnDismissListener() {
        return mOnDismissListener;
    }

    public void setOnDismissListener(OnDismissListener onDismissListener) {
        mOnDismissListener = onDismissListener;
    }

    public static class Builder<T extends BlurPopupWindow> {
        private static final String TAG = "BlurPopupWindow.Builder";
        protected Context mContext;
        private View mContentView;
        private int mTintColor;
        private float mBlurRadius;
        private float mScaleRatio;
        private long mAnimationDuration;
        private boolean mDismissOnTouchBackground = true;
        private boolean mDismissOnClickBack = true;
        private int mGravity = -1;
        private OnDismissListener mOnDismissListener;

        public Builder(Context context) {
            mContext = context;

            mBlurRadius = BlurPopupWindow.DEFAULT_BLUR_RADIUS;
            mScaleRatio = BlurPopupWindow.DEFAULT_SCALE_RATIO;
            mAnimationDuration = BlurPopupWindow.DEFAULT_ANIMATION_DURATION;
        }

        public Builder<T> setContentView(View contentView) {
            mContentView = contentView;
            return this;
        }

        public Builder<T> setContentView(int resId) {
            View view = LayoutInflater.from(mContext).inflate(resId, new FrameLayout(mContext), false);
            mContentView = view;
            return this;
        }

        public Builder<T> bindContentViewClickListener(View.OnClickListener listener) {
            if (mContentView != null) {
                mContentView.setClickable(true);
                mContentView.setOnClickListener(listener);
            }
            return this;
        }

        public Builder<T> bindClickListener(View.OnClickListener listener, int... views) {
            if (mContentView != null) {
                for (int viewId : views) {
                    View view = mContentView.findViewById(viewId);
                    if (view != null) {
                        view.setOnClickListener(listener);
                    }
                }
            }
            return this;
        }

        public Builder<T> setGravity(int gravity) {
            mGravity = gravity;
            return this;
        }

        public Builder<T> setTintColor(int tintColor) {
            mTintColor = tintColor;
            return this;
        }

        public Builder<T> setScaleRatio(float scaleRatio) {
            if (scaleRatio <= 0 || scaleRatio > 1) {
                Log.w(TAG, "scaleRatio invalid: " + scaleRatio + ". It can only be (0, 1]");
                return this;
            }
            mScaleRatio = scaleRatio;
            return this;
        }

        public Builder<T> setBlurRadius(float blurRadius) {
            if (blurRadius < 0 || blurRadius > 25) {
                Log.w(TAG, "blurRadius invalid: " + blurRadius + ". It can only be [0, 25]");
                return this;
            }
            mBlurRadius = blurRadius;
            return this;
        }

        public Builder<T> setAnimationDuration(long animatingDuration) {
            if (animatingDuration < 0) {
                Log.w(TAG, "animatingDuration invalid: " + animatingDuration + ". It can only be (0, ..)");
                return this;
            }
            mAnimationDuration = animatingDuration;
            return this;
        }

        public Builder<T> setDismissOnTouchBackground(boolean dismissOnTouchBackground) {
            mDismissOnTouchBackground = dismissOnTouchBackground;
            return this;
        }

        public Builder<T> setDismissOnClickBack(boolean dismissOnClickBack) {
            mDismissOnClickBack = dismissOnClickBack;
            return this;
        }

        public Builder<T> setOnDismissListener(OnDismissListener onDismissListener) {
            mOnDismissListener = onDismissListener;
            return this;
        }

        protected T createPopupWindow() {
            //noinspection unchecked
            return (T) new BlurPopupWindow(mContext);
        }

        public T build() {
            T popupWindow = createPopupWindow();
            if (mContentView != null) {
                ViewGroup.LayoutParams layoutParams = mContentView.getLayoutParams();
                if (layoutParams == null || !(layoutParams instanceof FrameLayout.LayoutParams)) {
                    layoutParams = new FrameLayout.LayoutParams(layoutParams.width, layoutParams.height);
                }
                if (mGravity != -1) {
                    ((LayoutParams) layoutParams).gravity = mGravity;
                }
                mContentView.setLayoutParams(layoutParams);
                popupWindow.setContentView(mContentView);
            }
            popupWindow.setTintColor(mTintColor);
            popupWindow.setAnimationDuration(mAnimationDuration);
            popupWindow.setBlurRadius(mBlurRadius);
            popupWindow.setScaleRatio(mScaleRatio);
            popupWindow.setDismissOnTouchBackground(mDismissOnTouchBackground);
            popupWindow.setDismissOnClickBack(mDismissOnClickBack);
            popupWindow.setOnDismissListener(mOnDismissListener);
            return popupWindow;
        }
    }

    private final static class BlurTask extends AsyncTask<Void, Void, Bitmap> {

        private WeakReference<Context> mContextRef;
        private WeakReference<BlurPopupWindow> mPopupWindowRef;
        private Bitmap mSourceBitmap;
        private BlurTaskCallback mBlurTaskCallback;

        interface BlurTaskCallback {
            void onBlurFinish(Bitmap bitmap);
        }

        BlurTask(View sourceView, int statusBarHeight, int navigationBarheight, BlurPopupWindow popupWindow, BlurTaskCallback blurTaskCallback) {
            mContextRef = new WeakReference<>(sourceView.getContext());
            mPopupWindowRef = new WeakReference<>(popupWindow);
            mBlurTaskCallback = blurTaskCallback;

            int height = sourceView.getHeight() - statusBarHeight - navigationBarheight;
            if (height < 0) {
                height = sourceView.getHeight();
            }

            Drawable background = sourceView.getBackground();
            mSourceBitmap = Bitmap.createBitmap(sourceView.getWidth(), height, Bitmap.Config.ARGB_8888);
            Canvas canvas = new Canvas(mSourceBitmap);
            int saveCount = 0;
            if (statusBarHeight != 0) {
                saveCount = canvas.save();
                canvas.translate(0, -statusBarHeight);
            }
            if (popupWindow.getBlurRadius() > 0) {
                if (background == null) {
                    canvas.drawColor(0xffffffff);
                }
                sourceView.draw(canvas);
            }
            if (popupWindow.getTintColor() != 0) {
                canvas.drawColor(popupWindow.getTintColor());
            }
            if (statusBarHeight != 0 && saveCount != 0) {
                canvas.restoreToCount(saveCount);
            }
        }

        @Override
        protected Bitmap doInBackground(Void... params) {
            Context context = mContextRef.get();
            BlurPopupWindow popupWindow = mPopupWindowRef.get();
            if (context == null || popupWindow == null) {
                return null;
            }
            float scaleRatio = popupWindow.getScaleRatio();
            if (popupWindow.getBlurRadius() == 0) {
                return mSourceBitmap;
            }
            Bitmap scaledBitmap = Bitmap.createScaledBitmap(mSourceBitmap, (int) (mSourceBitmap.getWidth() * scaleRatio), (int) (mSourceBitmap.getHeight() * scaleRatio), false);
            float radius = popupWindow.getBlurRadius();
            Bitmap blurred = BlurUtils.blur(context, scaledBitmap, radius);
            return Bitmap.createScaledBitmap(blurred, mSourceBitmap.getWidth(), mSourceBitmap.getHeight(), true);
        }

        @Override
        protected void onPostExecute(Bitmap bitmap) {
            BlurPopupWindow popupWindow = mPopupWindowRef.get();
            if (popupWindow != null && popupWindow.getAnchorView() != null) {
                Canvas canvas = new Canvas(bitmap);
                View anchorView = popupWindow.getAnchorView();
                int[] location = new int[2];
                anchorView.getLocationInWindow(location);
                canvas.save();
                canvas.translate(location[0], location[1]);
                popupWindow.getAnchorView().draw(canvas);
                canvas.restore();
            }
            if (mBlurTaskCallback != null) {
                mBlurTaskCallback.onBlurFinish(bitmap);
            }
        }
    }

    private static int getNaviHeight(Activity activity) {
        if (activity == null) {
            return 0;
        }
        Display display = activity.getWindowManager().getDefaultDisplay();
        int contentHeight = activity.getResources().getDisplayMetrics().heightPixels;
        int realHeight = 0;
        if (android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
            final DisplayMetrics metrics = new DisplayMetrics();
            display.getRealMetrics(metrics);
            realHeight = metrics.heightPixels;
        } else {
            try {
                Method mGetRawH = Display.class.getMethod("getRawHeight");
                realHeight = (Integer) mGetRawH.invoke(display);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        return realHeight - contentHeight;
    }

}